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

Commit 13c27ae9 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Remove token behind fullscreen activity from unknown visibility

When launching activity while keyguard is locked, their window tokens
will be added into unknown visibility controller to smooth the transition.
But if there are multiple starting activities without attached process,
only the last started activity will be resumed. Then the prior activities
are left in unknown visibility and cause transition timeout.

It is resolved by removing the unknown visibility records that won't be
visible when resuming top activity. Also consolidate shouldBeVisible and
shouldBeVisibleIgnoringKeyguard to reduce duplicated checking.

Bug: 123540470
Bug: 141826987
Test: atest ActivityStackTests# \
            testClearUnknownAppVisibilityBehindFullscreenActivity

Change-Id: Ie6d11e856a2b81a39222a0d96644712ce8ddf955
parent d6e55406
Loading
Loading
Loading
Loading
+36 −26
Original line number Original line Diff line number Diff line
@@ -4439,34 +4439,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        removeStartingWindow();
        removeStartingWindow();
    }
    }


    void notifyUnknownVisibilityLaunched() {

        // No display activities never add a window, so there is no point in waiting them for
        // relayout.
        if (!noDisplay && getDisplayContent() != null) {
            getDisplayContent().mUnknownAppVisibilityController.notifyLaunched(this);
        }
    }

    /**
    /**
     * @return true if the input activity should be made visible, ignoring any effect Keyguard
     * Suppress transition until the new activity becomes ready, otherwise the keyguard can appear
     * might have on the visibility
     * for a short amount of time before the new process with the new activity had the ability to
     *
     * set its showWhenLocked flags.
     * TODO(b/123540470): Combine this method and {@link #shouldBeVisible(boolean)}.
     *
     * @see {@link ActivityStack#checkKeyguardVisibility}
     */
     */
    boolean shouldBeVisibleIgnoringKeyguard(boolean behindFullscreenActivity) {
    void notifyUnknownVisibilityLaunchedForKeyguardTransition() {
        if (!okToShowLocked()) {
        // No display activities never add a window, so there is no point in waiting them for
            return false;
        // relayout.
        if (noDisplay || !mStackSupervisor.getKeyguardController().isKeyguardLocked()) {
            return;
        }
        }


        return !behindFullscreenActivity || mLaunchTaskBehind;
        mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(this);
    }
    }


    boolean shouldBeVisible(boolean behindFullscreenActivity) {
    /** @return {@code true} if this activity should be made visible. */
    boolean shouldBeVisible(boolean behindFullscreenActivity, boolean ignoringKeyguard) {
        // Check whether activity should be visible without Keyguard influence
        // Check whether activity should be visible without Keyguard influence
        visibleIgnoringKeyguard = shouldBeVisibleIgnoringKeyguard(behindFullscreenActivity);
        visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind)
                && okToShowLocked();

        if (ignoringKeyguard) {
            return visibleIgnoringKeyguard;
        }


        final ActivityStack stack = getActivityStack();
        final ActivityStack stack = getActivityStack();
        if (stack == null) {
        if (stack == null) {
@@ -4495,9 +4491,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
            return false;
            return false;
        }
        }


        // TODO: Use real value of behindFullscreenActivity calculated using the same logic in
        final boolean behindFullscreenActivity = stack.checkBehindFullscreenActivity(
        // ActivityStack#ensureActivitiesVisibleLocked().
                this, null /* handleBehindFullscreenActivity */);
        return shouldBeVisible(!stack.shouldBeVisible(null /* starting */));
        return shouldBeVisible(behindFullscreenActivity, false /* ignoringKeyguard */);
    }
    }


    void makeVisibleIfNeeded(ActivityRecord starting, boolean reportToClient) {
    void makeVisibleIfNeeded(ActivityRecord starting, boolean reportToClient) {
@@ -5538,12 +5534,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        }
        }
    }
    }


    void removeOrphanedStartingWindow(boolean behindFullscreenActivity) {
    /**
        if (mStartingWindowState == STARTING_WINDOW_SHOWN && behindFullscreenActivity) {
     * If any activities below the top running one are in the INITIALIZING state and they have a
     * starting window displayed then remove that starting window. It is possible that the activity
     * in this state will never resumed in which case that starting window will be orphaned.
     * <p>
     * It should only be called if this activity is behind other fullscreen activity.
     */
    void cancelInitializing() {
        if (mStartingWindowState == STARTING_WINDOW_SHOWN) {
            // Remove orphaned starting window.
            if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this);
            if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this);
            mStartingWindowState = STARTING_WINDOW_REMOVED;
            mStartingWindowState = STARTING_WINDOW_REMOVED;
            removeStartingWindow();
            removeStartingWindow();
        }
        }
        if (isState(INITIALIZING) && !shouldBeVisible(
                true /* behindFullscreenActivity */, true /* ignoringKeyguard */)) {
            // Remove the unknown visibility record because an invisible activity shouldn't block
            // the keyguard transition.
            mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
        }
    }
    }


    void postWindowRemoveStartingWindowCleanup(WindowState win) {
    void postWindowRemoveStartingWindowCleanup(WindowState win) {
+46 −24
Original line number Original line Diff line number Diff line
@@ -162,6 +162,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Objects;
import java.util.Set;
import java.util.Set;
import java.util.function.Consumer;


/**
/**
 * State and management of a single stack of activities.
 * State and management of a single stack of activities.
@@ -2118,14 +2119,21 @@ class ActivityStack extends ConfigurationContainer {
                    }
                    }
                    aboveTop = false;
                    aboveTop = false;


                    final boolean reallyVisible = r.shouldBeVisible(behindFullscreenActivity,
                            false /* ignoringKeyguard */);
                    // Check whether activity should be visible without Keyguard influence
                    // Check whether activity should be visible without Keyguard influence
                    final boolean visibleIgnoringKeyguard = r.shouldBeVisibleIgnoringKeyguard(
                    if (r.visibleIgnoringKeyguard) {
                            behindFullscreenActivity);
                        if (r.occludesParent()) {
                    final boolean reallyVisible = r.shouldBeVisible(behindFullscreenActivity);
                            // At this point, nothing else needs to be shown in this task.
                    if (visibleIgnoringKeyguard) {
                            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
                        behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
                                    + " stackVisible=" + stackShouldBeVisible
                                behindFullscreenActivity, r);
                                    + " behindFullscreen=" + behindFullscreenActivity);
                            behindFullscreenActivity = true;
                        } else {
                            behindFullscreenActivity = false;
                        }
                        }
                    }

                    if (reallyVisible) {
                    if (reallyVisible) {
                        if (r.finishing) {
                        if (r.finishing) {
                            continue;
                            continue;
@@ -2338,18 +2346,6 @@ class ActivityStack extends ConfigurationContainer {
        return false;
        return false;
    }
    }


    private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity,
            ActivityRecord r) {
        if (r.occludesParent()) {
            if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
                        + " stackInvisible=" + stackInvisible
                        + " behindFullscreenActivity=" + behindFullscreenActivity);
            // At this point, nothing else needs to be shown in this task.
            behindFullscreenActivity = true;
        }
        return behindFullscreenActivity;
    }

    void convertActivityToTranslucent(ActivityRecord r) {
    void convertActivityToTranslucent(ActivityRecord r) {
        mTranslucentActivityWaiting = r;
        mTranslucentActivityWaiting = r;
        mUndrawnActivitiesBelowTopTranslucent.clear();
        mUndrawnActivitiesBelowTopTranslucent.clear();
@@ -2400,14 +2396,22 @@ class ActivityStack extends ConfigurationContainer {
        }
        }
    }
    }


    /** If any activities below the top running one are in the INITIALIZING state and they have a
    /** @see ActivityRecord#cancelInitializing() */
     * starting window displayed then remove that starting window. It is possible that the activity
     * in this state will never resumed in which case that starting window will be orphaned. */
    void cancelInitializingActivities() {
    void cancelInitializingActivities() {
        final ActivityRecord topActivity = topRunningActivityLocked();
        boolean aboveTop = true;
        // We don't want to clear starting window for activities that aren't behind fullscreen
        // We don't want to clear starting window for activities that aren't behind fullscreen
        // activities as we need to display their starting window until they are done initializing.
        // activities as we need to display their starting window until they are done initializing.
        checkBehindFullscreenActivity(null /* toCheck */, ActivityRecord::cancelInitializing);
    }

    /**
     * If an activity {@param toCheck} is given, this method returns {@code true} if the activity
     * is occluded by any fullscreen activity. If there is no {@param toCheck} and the handling
     * function {@param handleBehindFullscreenActivity} is given, this method will pass all occluded
     * activities to the function.
     */
    boolean checkBehindFullscreenActivity(ActivityRecord toCheck,
            Consumer<ActivityRecord> handleBehindFullscreenActivity) {
        boolean aboveTop = true;
        boolean behindFullscreenActivity = false;
        boolean behindFullscreenActivity = false;


        if (!shouldBeVisible(null)) {
        if (!shouldBeVisible(null)) {
@@ -2417,22 +2421,40 @@ class ActivityStack extends ConfigurationContainer {
            behindFullscreenActivity = true;
            behindFullscreenActivity = true;
        }
        }


        final boolean handlingOccluded = toCheck == null && handleBehindFullscreenActivity != null;
        if (!handlingOccluded && behindFullscreenActivity) {
            return true;
        }

        final ActivityRecord topActivity = topRunningActivityLocked();
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            final TaskRecord task = mTaskHistory.get(taskNdx);
            final TaskRecord task = mTaskHistory.get(taskNdx);
            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
            for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
                final ActivityRecord r = task.getChildAt(activityNdx);
                final ActivityRecord r = task.getChildAt(activityNdx);
                if (aboveTop) {
                if (aboveTop) {
                    if (r == topActivity) {
                    if (r == topActivity) {
                        if (r == toCheck) {
                            // It is the top activity in a visible stack.
                            return false;
                        }
                        aboveTop = false;
                        aboveTop = false;
                    }
                    }
                    behindFullscreenActivity |= r.occludesParent();
                    behindFullscreenActivity |= r.occludesParent();
                    continue;
                    continue;
                }
                }


                r.removeOrphanedStartingWindow(behindFullscreenActivity);
                if (handlingOccluded) {
                    handleBehindFullscreenActivity.accept(r);
                } else if (r == toCheck) {
                    return behindFullscreenActivity;
                } else if (behindFullscreenActivity) {
                    // It is occluded before {@param toCheck} is found.
                    return true;
                }
                behindFullscreenActivity |= r.occludesParent();
                behindFullscreenActivity |= r.occludesParent();
            }
            }
        }
        }
        return behindFullscreenActivity;
    }
    }


    /**
    /**
+2 −9
Original line number Original line Diff line number Diff line
@@ -752,9 +752,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
                andResume = false;
                andResume = false;
            }
            }


            if (getKeyguardController().isKeyguardLocked()) {
            r.notifyUnknownVisibilityLaunchedForKeyguardTransition();
                r.notifyUnknownVisibilityLaunched();
            }


            // Have the window manager re-evaluate the orientation of the screen based on the new
            // Have the window manager re-evaluate the orientation of the screen based on the new
            // activity order.  Note that as a result of this, it can call back into the activity
            // activity order.  Note that as a result of this, it can call back into the activity
@@ -992,12 +990,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
            knownToBeDead = true;
            knownToBeDead = true;
        }
        }


        // Suppress transition until the new activity becomes ready, otherwise the keyguard can
        r.notifyUnknownVisibilityLaunchedForKeyguardTransition();
        // appear for a short amount of time before the new process with the new activity had the
        // ability to set its showWhenLocked flags.
        if (getKeyguardController().isKeyguardLocked()) {
            r.notifyUnknownVisibilityLaunched();
        }


        final boolean isTop = andResume && r.isTopRunningActivity();
        final boolean isTop = andResume && r.isTopRunningActivity();
        mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity");
        mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity");
+39 −1
Original line number Original line Diff line number Diff line
@@ -1139,13 +1139,51 @@ public class ActivityStackTests extends ActivityTestsBase {
        assertThat(result).isEqualTo(taskTop);
        assertThat(result).isEqualTo(taskTop);
    }
    }


    @Test
    public void testClearUnknownAppVisibilityBehindFullscreenActivity() {
        final UnknownAppVisibilityController unknownAppVisibilityController =
                mDefaultDisplay.mDisplayContent.mUnknownAppVisibilityController;
        final KeyguardController keyguardController = mSupervisor.getKeyguardController();
        doReturn(true).when(keyguardController).isKeyguardLocked();

        // Start 2 activities that their processes have not yet started.
        final ActivityRecord[] activities = new ActivityRecord[2];
        mSupervisor.beginDeferResume();
        for (int i = 0; i < activities.length; i++) {
            final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build();
            activities[i] = r;
            doReturn(null).when(mService).getProcessController(
                    eq(r.processName), eq(r.info.applicationInfo.uid));
            r.setState(ActivityStack.ActivityState.INITIALIZING, "test");
            // Ensure precondition that the activity is opaque.
            assertTrue(r.occludesParent());
            mSupervisor.startSpecificActivityLocked(r, false /* andResume */,
                    false /* checkConfig */);
        }
        mSupervisor.endDeferResume();

        doReturn(false).when(mService).isBooting();
        doReturn(true).when(mService).isBooted();
        // 2 activities are started while keyguard is locked, so they are waiting to be resolved.
        assertFalse(unknownAppVisibilityController.allResolved());

        // Assume the top activity is going to resume and
        // {@link RootActivityContainer#cancelInitializingActivities} should clear the unknown
        // visibility records that are occluded.
        mStack.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */);
        // Assume the top activity relayouted, just remove it directly.
        unknownAppVisibilityController.appRemovedOrHidden(activities[1]);
        // All unresolved records should be removed.
        assertTrue(unknownAppVisibilityController.allResolved());
    }

    @Test
    @Test
    public void testNonTopVisibleActivityNotResume() {
    public void testNonTopVisibleActivityNotResume() {
        final ActivityRecord nonTopVisibleActivity =
        final ActivityRecord nonTopVisibleActivity =
                new ActivityBuilder(mService).setTask(mTask).build();
                new ActivityBuilder(mService).setTask(mTask).build();
        new ActivityBuilder(mService).setTask(mTask).build();
        new ActivityBuilder(mService).setTask(mTask).build();
        doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
        doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
        doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleIgnoringKeyguard(anyBoolean());
        doReturn(true).when(nonTopVisibleActivity).shouldBeVisible(anyBoolean(), anyBoolean());
        doNothing().when(mSupervisor).startSpecificActivityLocked(any(), anyBoolean(),
        doNothing().when(mSupervisor).startSpecificActivityLocked(any(), anyBoolean(),
                anyBoolean());
                anyBoolean());