mWindowManager.deferSurfaceLayout();
+ // This will clear the pinned stack by moving an existing task to the full screen stack,
+ // ensuring only one task is present.
+ moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+
// Need to make sure the pinned stack exist so we can resize it below...
final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
import android.util.DisplayMetrics;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.util.XmlUtils;
final Rect bounds = updateOverrideConfigurationFromLaunchBounds();
final Configuration overrideConfig = getOverrideConfiguration();
- mWindowContainerController = new TaskWindowContainerController(taskId, this,
+ setWindowContainerController(new TaskWindowContainerController(taskId, this,
getStack().getWindowContainerController(), userId, bounds, overrideConfig,
mResizeMode, mSupportsPictureInPicture, isHomeTask(), onTop, showForAllUsers,
- lastTaskDescription);
+ lastTaskDescription));
+ }
+
+ /**
+ * Should only be invoked from {@link #createWindowContainer(boolean, boolean)}.
+ */
+ @VisibleForTesting
+ protected void setWindowContainerController(TaskWindowContainerController controller) {
+ if (mWindowContainerController != null) {
+ throw new IllegalArgumentException("Window container=" + mWindowContainerController
+ + " already created for task=" + this);
+ }
+
+ mWindowContainerController = controller;
}
void removeWindowContainer() {
package com.android.server.am;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import android.content.ComponentName;
import android.platform.test.annotations.Presubmit;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityRecordTests extends ActivityTestsBase {
+ private static final int TEST_STACK_ID = 100;
+
private final ComponentName testActivityComponent =
ComponentName.unflattenFromString("com.foo/.BarActivity");
@Test
public void testStackCleanupOnClearingTask() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord record = createActivity(service, testActivityComponent, task);
record.setTask(null);
- assertTrue(testStack.onActivityRemovedFromStackInvocationCount() == 1);
+ assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID), 1);
}
@Test
public void testStackCleanupOnActivityRemoval() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord record = createActivity(service, testActivityComponent, task);
task.removeActivity(record);
- assertTrue(testStack.onActivityRemovedFromStackInvocationCount() == 1);
+ assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID), 1);
}
@Test
public void testStackCleanupOnTaskRemoval() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord record = createActivity(service, testActivityComponent, task);
- testStack.removeTask(task, null /*reason*/, ActivityStack.REMOVE_TASK_MODE_MOVING);
- assertTrue(testStack.onActivityRemovedFromStackInvocationCount() == 1);
+ service.mStackSupervisor.getStack(TEST_STACK_ID)
+ .removeTask(task, null /*reason*/, ActivityStack.REMOVE_TASK_MODE_MOVING);
+
+ // Stack should be gone on task removal.
+ assertNull(service.mStackSupervisor.getStack(TEST_STACK_ID));
}
@Test
public void testNoCleanupMovingActivityInSameStack() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord oldTask = createTask(service, testActivityComponent, testStack);
+ final TaskRecord oldTask = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord record = createActivity(service, testActivityComponent, oldTask);
- final TaskRecord newTask = createTask(service, testActivityComponent, testStack);
+ final TaskRecord newTask = createTask(service, testActivityComponent, TEST_STACK_ID);
record.reparent(newTask, 0, null /*reason*/);
- assertTrue(testStack.onActivityRemovedFromStackInvocationCount() == 0);
+ assertEquals(getActivityRemovedFromStackCount(service, TEST_STACK_ID), 0);
+ }
+
+ private static int getActivityRemovedFromStackCount(ActivityManagerService service,
+ int stackId) {
+ final ActivityStack stack = service.mStackSupervisor.getStack(stackId);
+ if (stack instanceof ActivityStackReporter) {
+ return ((ActivityStackReporter) stack).onActivityRemovedFromStackInvocationCount();
+ }
+
+ return -1;
}
}
package com.android.server.am;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import android.content.ComponentName;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.runner.RunWith;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;
/**
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackSupervisorTests extends ActivityTestsBase {
+ private final ComponentName testActivityComponent =
+ ComponentName.unflattenFromString("com.foo/.BarActivity");
+
/**
* This test ensures that we do not try to restore a task based off an invalid task id. The
* stack supervisor is a test version so there will be no tasks present. We should expect
MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, 0 /*stackId*/);
assertNull(task);
}
+
+ /**
+ * This test ensures that an existing task in the pinned stack is moved to the fullscreen
+ * activity stack when a new task is added.
+ */
+ @Test
+ public void testReplacingTaskInPinnedStack() throws Exception {
+ final ActivityManagerService service = createActivityManagerService();
+ final TaskRecord firstTask = createTask(service, testActivityComponent,
+ FULLSCREEN_WORKSPACE_STACK_ID);
+ final ActivityRecord firstActivity = createActivity(service, testActivityComponent,
+ firstTask);
+ // Create a new task on the full screen stack
+ final TaskRecord secondTask = createTask(service, testActivityComponent,
+ FULLSCREEN_WORKSPACE_STACK_ID);
+ final ActivityRecord secondActivity = createActivity(service, testActivityComponent,
+ secondTask);
+ service.mStackSupervisor.setFocusStackUnchecked("testReplacingTaskInPinnedStack",
+ service.mStackSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID));
+
+ // Ensure full screen stack has both tasks.
+ ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, firstTask,
+ secondTask);
+
+ // Move first activity to pinned stack.
+ service.mStackSupervisor.moveActivityToPinnedStackLocked(firstActivity,
+ new Rect() /*sourceBounds*/, 0f /*aspectRatio*/, false, "initialMove");
+
+ // Ensure a task has moved over.
+ ensureStackPlacement(service.mStackSupervisor, PINNED_STACK_ID, firstTask);
+ ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, secondTask);
+
+ // Move second activity to pinned stack.
+ service.mStackSupervisor.moveActivityToPinnedStackLocked(secondActivity,
+ new Rect() /*sourceBounds*/, 0f /*aspectRatio*/ /*destBounds*/, false, "secondMove");
+
+ // Ensure stacks have swapped tasks.
+ ensureStackPlacement(service.mStackSupervisor, PINNED_STACK_ID, secondTask);
+ ensureStackPlacement(service.mStackSupervisor, FULLSCREEN_WORKSPACE_STACK_ID, firstTask);
+ }
+
+ private static void ensureStackPlacement(ActivityStackSupervisor supervisor, int stackId,
+ TaskRecord... tasks) {
+ final ActivityStack stack = supervisor.getStack(stackId);
+ final ArrayList<TaskRecord> stackTasks = stack.getAllTasks();
+ assertEquals(stackTasks.size(), tasks != null ? tasks.length : 0);
+
+ if (tasks == null) {
+ return;
+ }
+
+ for (TaskRecord task : tasks) {
+ assertTrue(stackTasks.contains(task));
+ }
+ }
}
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityStackTests extends ActivityTestsBase {
- private final ComponentName testActivityComponent =
+ private static final int TEST_STACK_ID = 100;
+ private static final ComponentName testActivityComponent =
ComponentName.unflattenFromString("com.foo/.BarActivity");
@Test
public void testEmptyTaskCleanupOnRemove() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
assertNotNull(task.getWindowContainerController());
- testStack.removeTask(task, "testEmptyTaskCleanupOnRemove",
- ActivityStack.REMOVE_TASK_MODE_DESTROYING);
+ service.mStackSupervisor.getStack(TEST_STACK_ID).removeTask(task,
+ "testEmptyTaskCleanupOnRemove", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
assertNull(task.getWindowContainerController());
}
@Test
public void testOccupiedTaskCleanupOnRemove() throws Exception {
final ActivityManagerService service = createActivityManagerService();
- final TestActivityStack testStack = new ActivityStackBuilder(service).build();
- final TaskRecord task = createTask(service, testActivityComponent, testStack);
+ final TaskRecord task = createTask(service, testActivityComponent, TEST_STACK_ID);
final ActivityRecord activityRecord = createActivity(service, testActivityComponent, task);
assertNotNull(task.getWindowContainerController());
- testStack.removeTask(task, "testOccupiedTaskCleanupOnRemove",
- ActivityStack.REMOVE_TASK_MODE_DESTROYING);
+ service.mStackSupervisor.getStack(TEST_STACK_ID).removeTask(task,
+ "testOccupiedTaskCleanupOnRemove", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
assertNotNull(task.getWindowContainerController());
}
}
package com.android.server.am;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
+
+import org.mockito.invocation.InvocationOnMock;
import android.app.ActivityManager;
import android.content.ComponentName;
import com.android.server.wm.AppWindowContainerController;
import com.android.server.wm.StackWindowController;
+import com.android.server.wm.TaskWindowContainerController;
import com.android.server.wm.WindowManagerService;
import com.android.server.wm.WindowTestUtils;
import org.junit.After;
protected ActivityManagerService createActivityManagerService() {
final ActivityManagerService service = new TestActivityManagerService(mContext);
- service.mWindowManager = WindowTestUtils.getWindowManagerService(mContext);
+ service.mWindowManager = WindowTestUtils.getMockWindowManagerService();
return service;
}
- protected static TestActivityStack createActivityStack(ActivityManagerService service,
+ protected static ActivityStack createActivityStack(ActivityManagerService service,
int stackId, int displayId, boolean onTop) {
if (service.mStackSupervisor instanceof TestActivityStackSupervisor) {
- final TestActivityStack stack = ((TestActivityStackSupervisor) service.mStackSupervisor)
+ return ((TestActivityStackSupervisor) service.mStackSupervisor)
.createTestStack(service, stackId, onTop);
- return stack;
}
return null;
}
protected static TaskRecord createTask(ActivityManagerService service,
- ComponentName component, ActivityStack stack) {
+ ComponentName component, int stackId) {
final ActivityInfo aInfo = new ActivityInfo();
aInfo.applicationInfo = new ApplicationInfo();
aInfo.applicationInfo.packageName = component.getPackageName();
final TaskRecord task = new TaskRecord(service, 0, aInfo, intent /*intent*/,
null /*_taskDescription*/, new ActivityManager.TaskThumbnailInfo());
+ final ActivityStack stack = service.mStackSupervisor.getStack(stackId,
+ true /*createStaticStackIfNeeded*/, true /*onTop*/);
stack.addTask(task, true, "creating test task");
task.setStack(stack);
- task.createWindowContainer(true, true);
+ task.setWindowContainerController(mock(TaskWindowContainerController.class));
return task;
}
+
/**
* An {@link ActivityManagerService} subclass which provides a test
* {@link ActivityStackSupervisor}.
protected static class TestActivityManagerService extends ActivityManagerService {
public TestActivityManagerService(Context context) {
super(context);
+ mSupportsMultiWindow = true;
+ mSupportsMultiDisplay = true;
+ mWindowManager = WindowTestUtils.getWindowManagerService(context);
}
@Override
protected static class TestActivityStackSupervisor extends ActivityStackSupervisor {
public TestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
super(service, looper);
+ mWindowManager = prepareMockWindowManager();
+ }
+
+ // No home stack is set.
+ @Override
+ void moveHomeStackToFront(String reason) {
}
// Invoked during {@link ActivityStack} creation.
void updateUIDsPresentOnDisplay() {
}
- public TestActivityStack createTestStack(ActivityManagerService service, int stackId,
- boolean onTop) {
+ // Just return the current front task.
+ @Override
+ ActivityStack getNextFocusableStackLocked(ActivityStack currentFocus) {
+ return mFocusedStack;
+ }
+
+ // Called when moving activity to pinned stack.
+ @Override
+ void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
+ boolean preserveWindows) {
+ }
+
+ public <T extends ActivityStack> T createTestStack(ActivityManagerService service,
+ int stackId, boolean onTop) {
final ActivityDisplay display = new ActivityDisplay();
final TestActivityContainer container =
new TestActivityContainer(service, stackId, display, onTop);
- return container.getStack();
+ mActivityContainers.put(stackId, container);
+ return (T) container.getStack();
+ }
+
+ @Override
+ protected <T extends ActivityStack> T getStack(int stackId,
+ boolean createStaticStackIfNeeded, boolean createOnTop) {
+ final T stack = super.getStack(stackId, createStaticStackIfNeeded, createOnTop);
+
+ if (stack != null || !createStaticStackIfNeeded) {
+ return stack;
+ }
+
+ return createTestStack(mService, stackId, createOnTop);
}
private class TestActivityContainer extends ActivityContainer {
- private ActivityManagerService mService;
- private TestActivityStack mStack;
+ private final ActivityManagerService mService;
+
private boolean mOnTop;
+ private int mStackId;
+ private ActivityStack mStack;
TestActivityContainer(ActivityManagerService service, int stackId,
ActivityDisplay activityDisplay, boolean onTop) {
// we cannot set {@link mService} by the time the super constructor calling this
// method is invoked.
mOnTop = onTop;
+ mStackId = stackId;
}
- public TestActivityStack getStack() {
+ public ActivityStack getStack() {
if (mStack == null) {
- mStack = new TestActivityStack(this,
- new RecentTasks(mService, mService.mStackSupervisor), mOnTop);
+ final RecentTasks recents =
+ new RecentTasks(mService, mService.mStackSupervisor);
+ mStack = mStackId == ActivityManager.StackId.PINNED_STACK_ID
+ ? new PinnedActivityStack(this, recents, mOnTop)
+ : new TestActivityStack(this, recents, mOnTop);
}
return mStack;
}
}
+ private static WindowManagerService prepareMockWindowManager() {
+ final WindowManagerService service = mock(WindowManagerService.class);
+
+ doAnswer((InvocationOnMock invocationOnMock) -> {
+ final Runnable runnable = invocationOnMock.<Runnable>getArgument(0);
+ if (runnable != null) {
+ runnable.run();
+ }
+ return null;
+ }).when(service).inSurfaceTransaction(any());
+
+ return service;
+ }
+
+ protected interface ActivityStackReporter {
+ int onActivityRemovedFromStackInvocationCount();
+ }
+
/**
* Override of {@link ActivityStack} that tracks test metrics, such as the number of times a
* method is called. Note that its functionality depends on the implementations of the
* construction arguments.
*/
protected static class TestActivityStack<T extends StackWindowController>
- extends ActivityStack<T> {
+ extends ActivityStack<T> implements ActivityStackReporter {
private int mOnActivityRemovedFromStackCount = 0;
private T mContainerController;
TestActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
}
// Returns the number of times {@link #onActivityRemovedFromStack} has been called
+ @Override
public int onActivityRemovedFromStackInvocationCount() {
return mOnActivityRemovedFromStackCount;
}
}
}
+
protected static class ActivityStackBuilder {
private boolean mOnTop = true;
private int mStackId = 0;
return this;
}
- public TestActivityStack build() {
+ public ActivityStack build() {
return createActivityStack(mService, mStackId, mDisplayId, mOnTop);
}
}
return null;
}).when(am).notifyKeyguardFlagsChanged(any());
}
+
sWm = WindowManagerService.main(context, mock(InputManagerService.class), true, false,
false, new TestWindowManagerPolicy());
}
}
/**
+ * Retrieves an instance of a mock {@link WindowManagerService}.
+ */
+ public static WindowManagerService getMockWindowManagerService() {
+ return mock(WindowManagerService.class);
+ }
+
+ /**
* Creates a mock instance of {@link StackWindowController}.
*/
public static StackWindowController createMockStackWindowContainerController() {