diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 95bae2eb24a8e..9fb3e11446cee 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3802,7 +3802,11 @@ class ActivityStack extends ConfigurationContai // and the resumed activity is not yet visible, then hold off on // finishing until the resumed one becomes visible. - final ActivityRecord next = mStackSupervisor.topRunningActivityLocked(); + // The activity that we are finishing may be over the lock screen. In this case, we do not + // want to consider activities that cannot be shown on the lock screen as running and should + // proceed with finishing the activity if there is no valid next top running activity. + final ActivityRecord next = mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */); if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible) && next != null && !next.nowVisible) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 0dc2445109ea5..e5565dccc70ea 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1210,6 +1210,18 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } ActivityRecord topRunningActivityLocked() { + return topRunningActivityLocked(false /* considerKeyguardState */); + } + + /** + * Returns the top running activity in the focused stack. In the case the focused stack has no + * such activity, the next focusable stack on top of a display is returned. + * @param considerKeyguardState Indicates whether the locked state should be considered. if + * {@code true} and the keyguard is locked, only activities that + * can be shown on top of the keyguard will be considered. + * @return The top running activity. {@code null} if none is available. + */ + ActivityRecord topRunningActivityLocked(boolean considerKeyguardState) { final ActivityStack focusedStack = mFocusedStack; ActivityRecord r = focusedStack.topRunningActivityLocked(); if (r != null) { @@ -1228,16 +1240,33 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (display == null) { continue; } - for (int j = display.getChildCount() - 1; j >= 0; --j) { - final ActivityStack stack = display.getChildAt(j); - if (stack != focusedStack && stack.isTopStackOnDisplay() && stack.isFocusable()) { - r = stack.topRunningActivityLocked(); - if (r != null) { - return r; - } - } + + // TODO: We probably want to consider the top fullscreen stack as we could have a pinned + // stack on top. + final ActivityStack topStack = display.getTopStack(); + + // Only consider focusable top stacks other than the current focused one. + if (topStack == null || !topStack.isFocusable() || topStack == focusedStack) { + continue; + } + + final ActivityRecord topActivity = topStack.topRunningActivityLocked(); + + // Skip if no top activity. + if (topActivity == null) { + continue; + } + + final boolean keyguardLocked = getKeyguardController().isKeyguardLocked(); + + // This activity can be considered the top running activity if we are not + // considering the locked state, the keyguard isn't locked, or we can show when + // locked. + if (!considerKeyguardState || !keyguardLocked || topActivity.canShowWhenLocked()) { + return topActivity; } } + return null; } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java index 08b8af289bd82..9daea1afc505a 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; +import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -329,4 +330,52 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { REMOVE_TASK_MODE_DESTROYING); assertFalse(pinnedStack.isFocusable()); } + + /** + * Verifies the correct activity is returned when querying the top running activity with an + * empty focused stack. + */ + @Test + public void testNonFocusedTopRunningActivity() throws Exception { + // Create stack to hold focus + final ActivityStack focusedStack = mService.mStackSupervisor.getDefaultDisplay() + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + + final KeyguardController keyguard = mSupervisor.getKeyguardController(); + final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + final ActivityRecord activity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(stack).build(); + + mSupervisor.mFocusedStack = focusedStack; + + doAnswer((InvocationOnMock invocationOnMock) -> { + final SparseIntArray displayIds = invocationOnMock.getArgument(0); + displayIds.put(0, mSupervisor.getDefaultDisplay().mDisplayId); + return null; + }).when(mSupervisor.mWindowManager).getDisplaysInFocusOrder(any()); + + // Make sure the top running activity is not affected when keyguard is not locked + assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); + assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */)); + + // Check to make sure activity not reported when it cannot show on lock and lock is on. + doReturn(true).when(keyguard).isKeyguardLocked(); + assertEquals(activity, mService.mStackSupervisor.topRunningActivityLocked()); + assertEquals(null, mService.mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */)); + + // Add activity that should be shown on the keyguard. + final ActivityRecord showWhenLockedActivity = new ActivityBuilder(mService) + .setCreateTask(true) + .setStack(stack) + .setActivityFlags(FLAG_SHOW_WHEN_LOCKED) + .build(); + + // Ensure the show when locked activity is returned. + assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked()); + assertEquals(showWhenLockedActivity, mService.mStackSupervisor.topRunningActivityLocked( + true /* considerKeyguardState */)); + } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index f5e61a1db341b..1cd111fce0ec0 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -132,6 +132,7 @@ public class ActivityTestsBase { private int mUid; private boolean mCreateTask; private ActivityStack mStack; + private int mActivityFlags; ActivityBuilder(ActivityManagerService service) { mService = service; @@ -152,6 +153,11 @@ public class ActivityTestsBase { return this; } + ActivityBuilder setActivityFlags(int flags) { + mActivityFlags = flags; + return this; + } + ActivityBuilder setStack(ActivityStack stack) { mStack = stack; return this; @@ -186,6 +192,8 @@ public class ActivityTestsBase { aInfo.applicationInfo = new ApplicationInfo(); aInfo.applicationInfo.packageName = mComponent.getPackageName(); aInfo.applicationInfo.uid = mUid; + aInfo.flags |= mActivityFlags; + final ActivityRecord activity = new ActivityRecord(mService, null /* caller */, 0 /* launchedFromPid */, 0, null, intent, null, aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,