diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0d9570697dbfd..eeee062769d01 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4439,34 +4439,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A removeStartingWindow(); } - void notifyUnknownVisibilityLaunched() { - + /** + * Suppress transition until the new activity becomes ready, otherwise the keyguard can appear + * for a short amount of time before the new process with the new activity had the ability to + * set its showWhenLocked flags. + */ + void notifyUnknownVisibilityLaunchedForKeyguardTransition() { // 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 - * might have on the visibility - * - * TODO(b/123540470): Combine this method and {@link #shouldBeVisible(boolean)}. - * - * @see {@link ActivityStack#checkKeyguardVisibility} - */ - boolean shouldBeVisibleIgnoringKeyguard(boolean behindFullscreenActivity) { - if (!okToShowLocked()) { - return false; + 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 - visibleIgnoringKeyguard = shouldBeVisibleIgnoringKeyguard(behindFullscreenActivity); + visibleIgnoringKeyguard = (!behindFullscreenActivity || mLaunchTaskBehind) + && okToShowLocked(); + + if (ignoringKeyguard) { + return visibleIgnoringKeyguard; + } final ActivityStack stack = getActivityStack(); if (stack == null) { @@ -4495,9 +4491,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return false; } - // TODO: Use real value of behindFullscreenActivity calculated using the same logic in - // ActivityStack#ensureActivitiesVisibleLocked(). - return shouldBeVisible(!stack.shouldBeVisible(null /* starting */)); + final boolean behindFullscreenActivity = stack.checkBehindFullscreenActivity( + this, null /* handleBehindFullscreenActivity */); + return shouldBeVisible(behindFullscreenActivity, false /* ignoringKeyguard */); } 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. + *

+ * 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); mStartingWindowState = STARTING_WINDOW_REMOVED; 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) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index a2353a0226310..8e3995bfd8724 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -162,6 +162,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; /** * State and management of a single stack of activities. @@ -2118,14 +2119,21 @@ class ActivityStack extends ConfigurationContainer { } aboveTop = false; + final boolean reallyVisible = r.shouldBeVisible(behindFullscreenActivity, + false /* ignoringKeyguard */); // Check whether activity should be visible without Keyguard influence - final boolean visibleIgnoringKeyguard = r.shouldBeVisibleIgnoringKeyguard( - behindFullscreenActivity); - final boolean reallyVisible = r.shouldBeVisible(behindFullscreenActivity); - if (visibleIgnoringKeyguard) { - behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible, - behindFullscreenActivity, r); + if (r.visibleIgnoringKeyguard) { + if (r.occludesParent()) { + // At this point, nothing else needs to be shown in this task. + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r + + " stackVisible=" + stackShouldBeVisible + + " behindFullscreen=" + behindFullscreenActivity); + behindFullscreenActivity = true; + } else { + behindFullscreenActivity = false; + } } + if (reallyVisible) { if (r.finishing) { continue; @@ -2338,18 +2346,6 @@ class ActivityStack extends ConfigurationContainer { 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) { mTranslucentActivityWaiting = r; 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 - * 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. */ + /** @see ActivityRecord#cancelInitializing() */ void cancelInitializingActivities() { - final ActivityRecord topActivity = topRunningActivityLocked(); - boolean aboveTop = true; // 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. + 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 handleBehindFullscreenActivity) { + boolean aboveTop = true; boolean behindFullscreenActivity = false; if (!shouldBeVisible(null)) { @@ -2417,22 +2421,40 @@ class ActivityStack extends ConfigurationContainer { 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) { final TaskRecord task = mTaskHistory.get(taskNdx); for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = task.getChildAt(activityNdx); if (aboveTop) { if (r == topActivity) { + if (r == toCheck) { + // It is the top activity in a visible stack. + return false; + } aboveTop = false; } behindFullscreenActivity |= r.occludesParent(); 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(); } } + return behindFullscreenActivity; } /** diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 191e87056c05f..64351fb07aed1 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -752,9 +752,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { andResume = false; } - if (getKeyguardController().isKeyguardLocked()) { - r.notifyUnknownVisibilityLaunched(); - } + r.notifyUnknownVisibilityLaunchedForKeyguardTransition(); // 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 @@ -992,12 +990,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { knownToBeDead = true; } - // Suppress transition until the new activity becomes ready, otherwise the keyguard can - // 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(); - } + r.notifyUnknownVisibilityLaunchedForKeyguardTransition(); final boolean isTop = andResume && r.isTopRunningActivity(); mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity"); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index 2835c1fb59ae3..fcebb81fd658a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -1139,13 +1139,51 @@ public class ActivityStackTests extends ActivityTestsBase { 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 public void testNonTopVisibleActivityNotResume() { final ActivityRecord nonTopVisibleActivity = new ActivityBuilder(mService).setTask(mTask).build(); new ActivityBuilder(mService).setTask(mTask).build(); doReturn(false).when(nonTopVisibleActivity).attachedToProcess(); - doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleIgnoringKeyguard(anyBoolean()); + doReturn(true).when(nonTopVisibleActivity).shouldBeVisible(anyBoolean(), anyBoolean()); doNothing().when(mSupervisor).startSpecificActivityLocked(any(), anyBoolean(), anyBoolean());