Fix unable to cleanup a removed display

Assume a display supports system decoration and allows reparenting
stacks to another display when the display is removed. If there
exists a standard type finishing activity during removing the
display, the activity will be reparented and found the original
stack is empty, then home is started again on the removed display.

This change combines the condition ActivityDisplay.mRemoved into
ActivityStack.isAttached, and defers the resume during finishing
or reparenting, so the stacks in the removing display won't be
focusable and resumed.

Also keep original z-order of the reparented stacks and reduce
unnecessary resume, visibility update for each reparented stack.

Bug: 119093839
Test: atest ActivityDisplayTests#testNotResumeHomeStackOnRemovingDisplay

Change-Id: I7dea2582ce8bd4aa4a18e1d5d721bfb1d9027c6a
This commit is contained in:
Riddle Hsu
2018-11-06 17:23:15 +08:00
parent 44f74d111f
commit 402b440f32
4 changed files with 88 additions and 19 deletions

View File

@@ -1010,29 +1010,50 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
void remove() { void remove() {
final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove(); final boolean destroyContentOnRemoval = shouldDestroyContentOnRemove();
ActivityStack lastReparentedStack = null;
mPreferredTopFocusableStack = null;
// Stacks could be reparented from the removed display to other display. While // 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 // 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 // released (no more ActivityStack). But, we cannot release it at that moment or the
// related WindowContainer and WindowContainerController will also be removed. So, we // related WindowContainer and WindowContainerController will also be removed. So, we
// set display as removed after reparenting stack finished. // set display as removed after reparenting stack finished.
for (int i = mStacks.size() - 1; i >= 0; --i) { final ActivityDisplay toDisplay = mSupervisor.getDefaultDisplay();
final ActivityStack stack = mStacks.get(i); mSupervisor.beginDeferResume();
// Always finish non-standard type stacks. try {
if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) { int numStacks = mStacks.size();
stack.finishAllActivitiesLocked(true /* immediately */); // Keep the order from bottom to top.
} else { for (int stackNdx = 0; stackNdx < numStacks; stackNdx++) {
// If default display is in split-window mode, set windowing mode of the stack to final ActivityStack stack = mStacks.get(stackNdx);
// split-screen secondary. Otherwise, set the windowing mode to undefined by // Always finish non-standard type stacks.
// default to let stack inherited the windowing mode from the new display. if (destroyContentOnRemoval || !stack.isActivityTypeStandardOrUndefined()) {
int windowingMode = mSupervisor.getDefaultDisplay().hasSplitScreenPrimaryStack() stack.finishAllActivitiesLocked(true /* immediately */);
? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY : WINDOWING_MODE_UNDEFINED; } else {
mSupervisor.moveStackToDisplayLocked(stack.mStackId, DEFAULT_DISPLAY, true); // If default display is in split-window mode, set windowing mode of the stack
stack.setWindowingMode(windowingMode); // 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; 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(); releaseSelfIfNeeded();
mSupervisor.getKeyguardController().onDisplayRemoved(mDisplayId); mSupervisor.getKeyguardController().onDisplayRemoved(mDisplayId);
@@ -1071,7 +1092,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
|| mDisplayId == mSupervisor.mService.mVr2dDisplayId; || mDisplayId == mSupervisor.mService.mVr2dDisplayId;
} }
private boolean shouldDestroyContentOnRemove() { @VisibleForTesting
boolean shouldDestroyContentOnRemove() {
return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT; return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
} }

View File

@@ -729,7 +729,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
} }
/** Adds the stack to specified display and calls WindowManager to do the same. */ /** 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 // 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. // so that it end up in a compatible mode in the new display. e.g. split-screen secondary.
removeFromDisplay(); removeFromDisplay();
@@ -738,6 +738,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mTmpRect2.setEmpty(); mTmpRect2.setEmpty();
mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2, onTop); mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2, onTop);
postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : 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 */); adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */);
mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); mStackSupervisor.resumeFocusedStacksTopActivitiesLocked();
// Update visibility of activities before notifying WM. This way it won't try to resize // Update visibility of activities before notifying WM. This way it won't try to resize
@@ -1151,7 +1158,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
} }
final boolean isAttached() { final boolean isAttached() {
return getParent() != null; final ActivityDisplay display = getDisplay();
return display != null && !display.isRemoved();
} }
/** /**

View File

@@ -3172,7 +3172,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
+ " to its current displayId=" + displayId); + " 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. // TODO(multi-display): resize stacks properly if moved from split-screen.
} }
@@ -4562,14 +4562,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
/** /**
* Begin deferring resume to avoid duplicate resumes in one pass. * Begin deferring resume to avoid duplicate resumes in one pass.
*/ */
private void beginDeferResume() { void beginDeferResume() {
mDeferResumeCount++; mDeferResumeCount++;
} }
/** /**
* End deferring resume and determine if resume can be called. * End deferring resume and determine if resume can be called.
*/ */
private void endDeferResume() { void endDeferResume() {
mDeferResumeCount--; mDeferResumeCount--;
} }

View File

@@ -16,6 +16,7 @@
package com.android.server.wm; 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.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 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.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn; 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; import android.platform.test.annotations.Presubmit;
@@ -120,6 +126,39 @@ public class ActivityDisplayTests extends ActivityTestsBase {
assertTrue(stack2.isFocusedStackOnDisplay()); 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) { private ActivityStack createFullscreenStackWithSimpleActivityAt(ActivityDisplay display) {
final ActivityStack fullscreenStack = display.createStack( final ActivityStack fullscreenStack = display.createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP); WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, ON_TOP);