diff options
8 files changed, 215 insertions, 42 deletions
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 43ae4b22ba63..152d5f463988 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2878,6 +2878,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D 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); diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index f8a4d4b21414..d42b6a7647ce 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -48,6 +48,7 @@ import android.service.voice.IVoiceInteractionSession; 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; @@ -445,10 +446,23 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta 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() { diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index 54ecab3af542..f75d49cae574 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -16,7 +16,8 @@ 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; @@ -36,50 +37,61 @@ import org.junit.Test; @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; } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java index 8423affecd30..fc9ab9635c57 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -16,8 +16,14 @@ 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; @@ -25,6 +31,10 @@ 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; /** @@ -37,6 +47,9 @@ import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS @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 @@ -49,4 +62,59 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { 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)); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java index 1d80b336becc..f42abf1927f0 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java @@ -37,28 +37,27 @@ import org.junit.Test; @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()); } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 3bf0e5ff86aa..082708442032 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -17,6 +17,11 @@ 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; @@ -33,6 +38,7 @@ import com.android.server.AttributeCache; 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; @@ -64,16 +70,15 @@ public class ActivityTestsBase { 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; @@ -103,7 +108,7 @@ public class ActivityTestsBase { } 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(); @@ -113,13 +118,16 @@ public class ActivityTestsBase { 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}. @@ -127,6 +135,9 @@ public class ActivityTestsBase { protected static class TestActivityManagerService extends ActivityManagerService { public TestActivityManagerService(Context context) { super(context); + mSupportsMultiWindow = true; + mSupportsMultiDisplay = true; + mWindowManager = WindowTestUtils.getWindowManagerService(context); } @Override @@ -142,6 +153,12 @@ public class ActivityTestsBase { 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. @@ -149,18 +166,45 @@ public class ActivityTestsBase { 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) { @@ -174,12 +218,16 @@ public class ActivityTestsBase { // 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; @@ -187,13 +235,31 @@ public class ActivityTestsBase { } } + 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, @@ -208,6 +274,7 @@ public class ActivityTestsBase { } // Returns the number of times {@link #onActivityRemovedFromStack} has been called + @Override public int onActivityRemovedFromStackInvocationCount() { return mOnActivityRemovedFromStackCount; } @@ -225,6 +292,7 @@ public class ActivityTestsBase { } } + protected static class ActivityStackBuilder { private boolean mOnTop = true; private int mStackId = 0; @@ -251,7 +319,7 @@ public class ActivityTestsBase { return this; } - public TestActivityStack build() { + public ActivityStack build() { return createActivityStack(mService, mStackId, mDisplayId, mOnTop); } } diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index fbeda0a9191c..9392e8ee2b5c 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -90,6 +90,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { return null; }).when(am).notifyKeyguardFlagsChanged(any()); } + sWm = WindowManagerService.main(context, mock(InputManagerService.class), true, false, false, new TestWindowManagerPolicy()); } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java index 3a443575332e..ae3eb522837f 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java @@ -48,6 +48,13 @@ public class WindowTestUtils { } /** + * 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() { |