Loading services/core/java/com/android/server/am/ActivityRecord.java +4 −2 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET; import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.os.Build.VERSION_CODES.O; import static android.os.Process.SYSTEM_UID; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION; Loading Loading @@ -2212,12 +2213,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } /** * @return display id to which this record is attached, -1 if not attached. * @return display id to which this record is attached, * {@link android.view.Display#INVALID_DISPLAY} if not attached. */ int getDisplayId() { final ActivityStack stack = getStack(); if (stack == null) { return -1; return INVALID_DISPLAY; } return stack.mDisplayId; } Loading services/core/java/com/android/server/am/ActivityStackSupervisor.java +15 −2 Original line number Diff line number Diff line Loading @@ -2466,7 +2466,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) { if (r != null) { stack = (T) getValidLaunchStackOnDisplay(displayId, r, options); stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options); if (stack != null) { return stack; } Loading Loading @@ -2531,10 +2531,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * If there is no such stack, new dynamic stack can be created. * @param displayId Target display. * @param r Activity that should be launched there. * @param candidateTask The possible task the activity might be put in. * @return Existing stack if there is a valid one, new dynamic stack if it is valid or null. */ ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r, @Nullable ActivityOptions options) { @Nullable TaskRecord candidateTask, @Nullable ActivityOptions options) { final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId); if (activityDisplay == null) { throw new IllegalArgumentException( Loading @@ -2545,6 +2546,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } // If {@code r} is already in target display and its task is the same as the candidate task, // the intention should be getting a launch stack for the reusable activity, so we can use // the existing stack. if (r.getDisplayId() == displayId && r.getTask() == candidateTask) { return candidateTask.getStack(); } // Return the topmost valid stack on the display. for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) { final ActivityStack stack = activityDisplay.getChildAt(i); Loading @@ -2565,6 +2573,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r, @Nullable ActivityOptions options) { return getValidLaunchStackOnDisplay(displayId, r, null /* candidateTask */, options); } // TODO: Can probably be consolidated into getLaunchStack()... private boolean isValidLaunchStack(ActivityStack stack, int displayId, ActivityRecord r) { switch (stack.getActivityType()) { Loading services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java +57 −13 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM; import static com.android.server.am.ActivityDisplay.POSITION_TOP; import static com.android.server.am.ActivityTaskManagerService.ANIMATE; import static org.junit.Assert.assertEquals; Loading Loading @@ -562,23 +563,13 @@ public class ActivityStarterTests extends ActivityTestsBase { false /* mockGetLaunchStack */); // Create a secondary display at bottom. final TestActivityDisplay secondaryDisplay = spy(addNewActivityDisplayAt(POSITION_BOTTOM)); final TestActivityDisplay secondaryDisplay = spy(createNewActivityDisplay()); mSupervisor.addChild(secondaryDisplay, POSITION_BOTTOM); final ActivityStack stack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Create an activity record on the top of secondary display. final ComponentName componentName = ComponentName.createRelative( DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_PACKAGE_NAME + ".ReusableActivity"); final TaskRecord taskRecord = new TaskBuilder(mSupervisor) .setComponent(componentName) .setStack(stack) .build(); final ActivityRecord topActivityOnSecondaryDisplay = new ActivityBuilder(mService) .setComponent(componentName) .setLaunchMode(LAUNCH_SINGLE_TASK) .setTask(taskRecord) .build(); final ActivityRecord topActivityOnSecondaryDisplay = createSingleTaskActivityOn(stack); // Put an activity on default display as the top focused activity. new ActivityBuilder(mService).setCreateTask(true).build(); Loading @@ -599,6 +590,59 @@ public class ActivityStarterTests extends ActivityTestsBase { verify(secondaryDisplay, times(1)).createStack(anyInt(), anyInt(), anyBoolean()); } /** * This test ensures that when starting an existing non-top single task activity on secondary * display which is the top focused display, it should bring the task to front without creating * unused stack. */ @Test public void testBringTaskToFrontOnSecondaryDisplay() { final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetLaunchStack */); // Create a secondary display with an activity. final TestActivityDisplay secondaryDisplay = spy(createNewActivityDisplay()); mSupervisor.addChild(secondaryDisplay, POSITION_TOP); final ActivityRecord singleTaskActivity = createSingleTaskActivityOn( secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); // Create another activity on top of the secondary display. final ActivityStack topStack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final TaskRecord topTask = new TaskBuilder(mSupervisor).setStack(topStack).build(); new ActivityBuilder(mService).setTask(topTask).build(); // Start activity with the same intent as {@code singleTaskActivity} on secondary display. final ActivityOptions options = ActivityOptions.makeBasic() .setLaunchDisplayId(secondaryDisplay.mDisplayId); final int result = starter.setReason("testBringTaskToFrontOnSecondaryDisplay") .setIntent(singleTaskActivity.intent) .setActivityOptions(options.toBundle()) .execute(); // Ensure result is moving existing task to front. assertEquals(START_TASK_TO_FRONT, result); // Ensure secondary display only creates two stacks. verify(secondaryDisplay, times(2)).createStack(anyInt(), anyInt(), anyBoolean()); } private ActivityRecord createSingleTaskActivityOn(ActivityStack stack) { final ComponentName componentName = ComponentName.createRelative( DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_PACKAGE_NAME + ".SingleTaskActivity"); final TaskRecord taskRecord = new TaskBuilder(mSupervisor) .setComponent(componentName) .setStack(stack) .build(); return new ActivityBuilder(mService) .setComponent(componentName) .setLaunchMode(LAUNCH_SINGLE_TASK) .setTask(taskRecord) .build(); } /** * This test ensures that a reused top activity in the top focused stack is able to be * reparented to another display. Loading Loading
services/core/java/com/android/server/am/ActivityRecord.java +4 −2 Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET; import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.os.Build.VERSION_CODES.O; import static android.os.Process.SYSTEM_UID; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT; import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION; Loading Loading @@ -2212,12 +2213,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } /** * @return display id to which this record is attached, -1 if not attached. * @return display id to which this record is attached, * {@link android.view.Display#INVALID_DISPLAY} if not attached. */ int getDisplayId() { final ActivityStack stack = getStack(); if (stack == null) { return -1; return INVALID_DISPLAY; } return stack.mDisplayId; } Loading
services/core/java/com/android/server/am/ActivityStackSupervisor.java +15 −2 Original line number Diff line number Diff line Loading @@ -2466,7 +2466,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) { if (r != null) { stack = (T) getValidLaunchStackOnDisplay(displayId, r, options); stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options); if (stack != null) { return stack; } Loading Loading @@ -2531,10 +2531,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * If there is no such stack, new dynamic stack can be created. * @param displayId Target display. * @param r Activity that should be launched there. * @param candidateTask The possible task the activity might be put in. * @return Existing stack if there is a valid one, new dynamic stack if it is valid or null. */ ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r, @Nullable ActivityOptions options) { @Nullable TaskRecord candidateTask, @Nullable ActivityOptions options) { final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId); if (activityDisplay == null) { throw new IllegalArgumentException( Loading @@ -2545,6 +2546,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } // If {@code r} is already in target display and its task is the same as the candidate task, // the intention should be getting a launch stack for the reusable activity, so we can use // the existing stack. if (r.getDisplayId() == displayId && r.getTask() == candidateTask) { return candidateTask.getStack(); } // Return the topmost valid stack on the display. for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) { final ActivityStack stack = activityDisplay.getChildAt(i); Loading @@ -2565,6 +2573,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return null; } ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r, @Nullable ActivityOptions options) { return getValidLaunchStackOnDisplay(displayId, r, null /* candidateTask */, options); } // TODO: Can probably be consolidated into getLaunchStack()... private boolean isValidLaunchStack(ActivityStack stack, int displayId, ActivityRecord r) { switch (stack.getActivityType()) { Loading
services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java +57 −13 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM; import static com.android.server.am.ActivityDisplay.POSITION_TOP; import static com.android.server.am.ActivityTaskManagerService.ANIMATE; import static org.junit.Assert.assertEquals; Loading Loading @@ -562,23 +563,13 @@ public class ActivityStarterTests extends ActivityTestsBase { false /* mockGetLaunchStack */); // Create a secondary display at bottom. final TestActivityDisplay secondaryDisplay = spy(addNewActivityDisplayAt(POSITION_BOTTOM)); final TestActivityDisplay secondaryDisplay = spy(createNewActivityDisplay()); mSupervisor.addChild(secondaryDisplay, POSITION_BOTTOM); final ActivityStack stack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); // Create an activity record on the top of secondary display. final ComponentName componentName = ComponentName.createRelative( DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_PACKAGE_NAME + ".ReusableActivity"); final TaskRecord taskRecord = new TaskBuilder(mSupervisor) .setComponent(componentName) .setStack(stack) .build(); final ActivityRecord topActivityOnSecondaryDisplay = new ActivityBuilder(mService) .setComponent(componentName) .setLaunchMode(LAUNCH_SINGLE_TASK) .setTask(taskRecord) .build(); final ActivityRecord topActivityOnSecondaryDisplay = createSingleTaskActivityOn(stack); // Put an activity on default display as the top focused activity. new ActivityBuilder(mService).setCreateTask(true).build(); Loading @@ -599,6 +590,59 @@ public class ActivityStarterTests extends ActivityTestsBase { verify(secondaryDisplay, times(1)).createStack(anyInt(), anyInt(), anyBoolean()); } /** * This test ensures that when starting an existing non-top single task activity on secondary * display which is the top focused display, it should bring the task to front without creating * unused stack. */ @Test public void testBringTaskToFrontOnSecondaryDisplay() { final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetLaunchStack */); // Create a secondary display with an activity. final TestActivityDisplay secondaryDisplay = spy(createNewActivityDisplay()); mSupervisor.addChild(secondaryDisplay, POSITION_TOP); final ActivityRecord singleTaskActivity = createSingleTaskActivityOn( secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */)); // Create another activity on top of the secondary display. final ActivityStack topStack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); final TaskRecord topTask = new TaskBuilder(mSupervisor).setStack(topStack).build(); new ActivityBuilder(mService).setTask(topTask).build(); // Start activity with the same intent as {@code singleTaskActivity} on secondary display. final ActivityOptions options = ActivityOptions.makeBasic() .setLaunchDisplayId(secondaryDisplay.mDisplayId); final int result = starter.setReason("testBringTaskToFrontOnSecondaryDisplay") .setIntent(singleTaskActivity.intent) .setActivityOptions(options.toBundle()) .execute(); // Ensure result is moving existing task to front. assertEquals(START_TASK_TO_FRONT, result); // Ensure secondary display only creates two stacks. verify(secondaryDisplay, times(2)).createStack(anyInt(), anyInt(), anyBoolean()); } private ActivityRecord createSingleTaskActivityOn(ActivityStack stack) { final ComponentName componentName = ComponentName.createRelative( DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_PACKAGE_NAME + ".SingleTaskActivity"); final TaskRecord taskRecord = new TaskBuilder(mSupervisor) .setComponent(componentName) .setStack(stack) .build(); return new ActivityBuilder(mService) .setComponent(componentName) .setLaunchMode(LAUNCH_SINGLE_TASK) .setTask(taskRecord) .build(); } /** * This test ensures that a reused top activity in the top focused stack is able to be * reparented to another display. Loading