diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index 678d233237fe3..478a11a6de0e3 100644 --- a/services/core/java/com/android/server/wm/ActivityDisplay.java +++ b/services/core/java/com/android/server/wm/ActivityDisplay.java @@ -1031,29 +1031,50 @@ class ActivityDisplay extends ConfigurationContainer void remove() { final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove(); + ActivityStack lastReparentedStack = null; + mPreferredTopFocusableStack = null; // Stacks could be reparented from the removed display to other display. While // reparenting the last stack of the removed display, the remove display is ready to be // released (no more ActivityStack). But, we cannot release it at that moment or the // related WindowContainer and WindowContainerController will also be removed. So, we // set display as removed after reparenting stack finished. - for (int i = mStacks.size() - 1; i >= 0; --i) { - final ActivityStack stack = mStacks.get(i); - // Always finish non-standard type stacks. - if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) { - stack.finishAllActivitiesLocked(true /* immediately */); - } else { - // If default display is in split-window mode, set windowing mode of the stack to - // split-screen secondary. Otherwise, set the windowing mode to undefined by - // default to let stack inherited the windowing mode from the new display. - int windowingMode = mSupervisor.getDefaultDisplay().hasSplitScreenPrimaryStack() - ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_UNDEFINED; - mSupervisor.moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, true); - stack.setWindowingMode(windowingMode); + final ActivityDisplay toDisplay = mSupervisor.getDefaultDisplay(); + mSupervisor.beginDeferResume(); + try { + int numStacks = mStacks.size(); + // Keep the order from bottom to top. + for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) { + final ActivityStack stack = mStacks.get(stackNdx); + // Always finish non-standard type stacks. + if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) { + stack.finishAllActivitiesLocked(true /* immediately */); + } else { + // If default display is in split-window mode, set windowing mode of the stack + // to split-screen secondary. Otherwise, set the windowing mode to undefined by + // default to let stack inherited the windowing mode from the new display. + final int windowingMode = toDisplay.hasSplitScreenPrimaryStack() + ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + : WINDOWING_MODE_UNDEFINED; + stack.reparent(toDisplay, true /* onTop */, true /* displayRemoved */); + stack.setWindowingMode(windowingMode); + lastReparentedStack = stack; + } + // Stacks may be removed from this display. Ensure each stack will be processed and + // the loop will end. + stackNdx -= numStacks - mStacks.size(); + numStacks = mStacks.size(); } + } finally { + mSupervisor.endDeferResume(); } mRemoved = true; + // Only update focus/visibility for the last one because there may be many stacks are + // reparented and the intermediate states are unnecessary. + if (lastReparentedStack != null) { + lastReparentedStack.postReparent(); + } releaseSelfIfNeeded(); if (!mAllSleepTokens.isEmpty()) { @@ -1091,7 +1112,8 @@ class ActivityDisplay extends ConfigurationContainer || mDisplayId == mSupervisor.mService.mVr2dDisplayId; } - private boolean shouldDestroyContentOnRemove() { + @VisibleForTesting + boolean shouldDestroyContentOnRemove() { return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT; } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index c269f79470edf..74e1c4a5f3836 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -738,7 +738,7 @@ class ActivityStack extends ConfigurationContai } /** Adds the stack to specified display and calls WindowManager to do the same. */ - void reparent(ActivityDisplay activityDisplay, boolean onTop) { + void reparent(ActivityDisplay activityDisplay, boolean onTop, boolean displayRemoved) { // TODO: We should probably resolve the windowing mode for the stack on the new display here // so that it end up in a compatible mode in the new display. e.g. split-screen secondary. removeFromDisplay(); @@ -747,6 +747,13 @@ class ActivityStack extends ConfigurationContai mTmpRect2.setEmpty(); mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2, onTop); postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop); + if (!displayRemoved) { + postReparent(); + } + } + + /** Resume next focusable stack after reparenting to another display. */ + void postReparent() { adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */); mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); // Update visibility of activities before notifying WM. This way it won't try to resize @@ -1162,7 +1169,8 @@ class ActivityStack extends ConfigurationContai } final boolean isAttached() { - return getParent() != null; + final ActivityDisplay display = getDisplay(); + return display != null && !display.isRemoved(); } /** diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 9bcee8a3ab239..96a2e4177ff30 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -3173,7 +3173,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D + " to its current displayId=" + displayId); } - stack.reparent(activityDisplay, onTop); + stack.reparent(activityDisplay, onTop, false /* displayRemoved */); // TODO(multi-display): resize stacks properly if moved from split-screen. } @@ -4568,14 +4568,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** * Begin deferring resume to avoid duplicate resumes in one pass. */ - private void beginDeferResume() { + void beginDeferResume() { mDeferResumeCount++; } /** * End deferring resume and determine if resume can be called. */ - private void endDeferResume() { + void endDeferResume() { mDeferResumeCount--; } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java index d3f1a0a537604..7a9c8dc27e7d7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityDisplayTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -27,7 +28,12 @@ import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import android.platform.test.annotations.Presubmit; @@ -120,6 +126,39 @@ public class ActivityDisplayTests extends ActivityTestsBase { assertTrue(stack2.isFocusedStackOnDisplay()); } + /** + * Verifies {@link ActivityDisplay#remove} should not resume home stack on the removing display. + */ + @Test + public void testNotResumeHomeStackOnRemovingDisplay() { + // Create a display which supports system decoration and allows reparenting stacks to + // another display when the display is removed. + final ActivityDisplay display = spy(createNewActivityDisplay()); + doReturn(false).when(display).shouldDestroyContentOnRemove(); + doReturn(true).when(display).supportsSystemDecorations(); + mSupervisor.addChild(display, ActivityDisplay.POSITION_TOP); + + // Put home stack on the display. + final ActivityStack homeStack = display.createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + final TaskRecord task = new TaskBuilder(mSupervisor).setStack(homeStack).build(); + new ActivityBuilder(mService).setTask(task).build(); + display.removeChild(homeStack); + final ActivityStack spiedHomeStack = spy(homeStack); + display.addChild(spiedHomeStack, ActivityDisplay.POSITION_TOP); + reset(spiedHomeStack); + + // Put a finishing standard activity which will be reparented. + final ActivityStack stack = createFullscreenStackWithSimpleActivityAt(display); + stack.topRunningActivityLocked().makeFinishingLocked(); + + display.remove(); + + // The removed display should have no focused stack and its home stack should never resume. + assertNull(display.getFocusedStack()); + verify(spiedHomeStack, never()).resumeTopActivityUncheckedLocked(any(), any()); + } + private ActivityStack createFullscreenStackWithSimpleActivityAt(ActivityDisplay display) { final ActivityStack fullscreenStack = display.createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP);