Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 402b440f authored by Riddle Hsu's avatar Riddle Hsu
Browse files

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
parent 44f74d11
Loading
Loading
Loading
Loading
+36 −14
Original line number Diff line number Diff line
@@ -1010,29 +1010,50 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>

    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);
        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
                    // 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);
                    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();

        mSupervisor.getKeyguardController().onDisplayRemoved(mDisplayId);
@@ -1071,7 +1092,8 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack>
                || mDisplayId == mSupervisor.mService.mVr2dDisplayId;
    }

    private boolean shouldDestroyContentOnRemove() {
    @VisibleForTesting
    boolean shouldDestroyContentOnRemove() {
        return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
    }

+10 −2
Original line number Diff line number Diff line
@@ -729,7 +729,7 @@ class ActivityStack<T extends StackWindowController> 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();
@@ -738,6 +738,13 @@ class ActivityStack<T extends StackWindowController> 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
@@ -1151,7 +1158,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
    }

    final boolean isAttached() {
        return getParent() != null;
        final ActivityDisplay display = getDisplay();
        return display != null && !display.isRemoved();
    }

    /**
+3 −3
Original line number Diff line number Diff line
@@ -3172,7 +3172,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.
    }

@@ -4562,14 +4562,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--;
    }

+39 −0
Original line number Diff line number Diff line
@@ -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);