From 3ff5a0b44df949890a69d2451f8f057097655d09 Mon Sep 17 00:00:00 2001 From: Andrii Kulian Date: Thu, 16 May 2019 13:18:04 -0700 Subject: Always start launcher when empty home stack is being resumed If home stack becomes empty for some reason and we're trying to find and resume an activity in it - make sure to start home instead of looking for the next top focusable stack. This will make sure that home stack won't be left empty if a different display is currently focused or higher in z-order. There may be no activity in a home stack if launcher crashed, or at boot when it switches between different activities. Bug: 131440583 Test: atest WmTests:RootActivityContainerTests#testResumeFocusedStacksStartsHomeActivity_NoActivities Test: atest WmTests:RootActivityContainerTests#testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen Change-Id: I955d7d4827b2598f9c98cb30ec3fedac4e403a9d --- .../java/com/android/server/wm/ActivityStack.java | 36 ++++++++++----- .../server/wm/RootActivityContainerTests.java | 54 ++++++++++++++++++++++ 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 3d59e66d13ef..8337701f49a9 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -2635,7 +2635,7 @@ class ActivityStack extends ConfigurationContainer { if (!hasRunningActivity) { // There are no activities left in the stack, let's look somewhere else. - return resumeTopActivityInNextFocusableStack(prev, options, "noMoreActivities"); + return resumeNextFocusableActivityWhenStackIsEmpty(prev, options); } next.delayedResume = false; @@ -3036,21 +3036,33 @@ class ActivityStack extends ConfigurationContainer { return true; } - private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev, - ActivityOptions options, String reason) { - final ActivityStack nextFocusedStack = adjustFocusToNextFocusableStack(reason); - if (nextFocusedStack != null) { - // Try to move focus to the next visible stack with a running activity if this - // stack is not covering the entire screen or is on a secondary display (with no home - // stack). - return mRootActivityContainer.resumeFocusedStacksTopActivities(nextFocusedStack, prev, - null /* targetOptions */); + /** + * Resume the next eligible activity in a focusable stack when this one does not have any + * running activities left. The focus will be adjusted to the next focusable stack and + * top running activities will be resumed in all focusable stacks. However, if the current stack + * is a home stack - we have to keep it focused, start and resume a home activity on the current + * display instead to make sure that the display is not empty. + */ + private boolean resumeNextFocusableActivityWhenStackIsEmpty(ActivityRecord prev, + ActivityOptions options) { + final String reason = "noMoreActivities"; + + if (!isActivityTypeHome()) { + final ActivityStack nextFocusedStack = adjustFocusToNextFocusableStack(reason); + if (nextFocusedStack != null) { + // Try to move focus to the next visible stack with a running activity if this + // stack is not covering the entire screen or is on a secondary display with no home + // stack. + return mRootActivityContainer.resumeFocusedStacksTopActivities(nextFocusedStack, + prev, null /* targetOptions */); + } } - // Let's just start up the Launcher... + // If the current stack is a home stack, or if focus didn't switch to a different stack - + // just start up the Launcher... ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG_STATES, - "resumeTopActivityInNextFocusableStack: " + reason + ", go home"); + "resumeNextFocusableActivityWhenStackIsEmpty: " + reason + ", go home"); return mRootActivityContainer.resumeHomeActivity(prev, reason, mDisplayId); } diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index 0f7b35c4975c..4b13ea71199a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -34,6 +35,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityDisplay.POSITION_TOP; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; +import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.RootActivityContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; import static org.junit.Assert.assertEquals; @@ -394,6 +396,58 @@ public class RootActivityContainerTests extends ActivityTestsBase { eq(activity), eq(null /* targetOptions */)); } + /** + * Verify that home activity will be started on a display even if another display has a + * focusable activity. + */ + @Test + public void testResumeFocusedStacksStartsHomeActivity_NoActivities() { + mFullscreenStack.remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY) + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + + doReturn(true).when(mRootActivityContainer).resumeHomeActivity(any(), any(), anyInt()); + + mService.setBooted(true); + + // Trigger resume on all displays + mRootActivityContainer.resumeFocusedStacksTopActivities(); + + // Verify that home activity was started on the default display + verify(mRootActivityContainer).resumeHomeActivity(any(), any(), eq(DEFAULT_DISPLAY)); + } + + /** + * Verify that home activity will be started on a display even if another display has a + * focusable activity. + */ + @Test + public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() { + mFullscreenStack.remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY) + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + + // Create an activity on secondary display. + final TestActivityDisplay secondDisplay = addNewActivityDisplayAt( + ActivityDisplay.POSITION_TOP); + final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build(); + new ActivityBuilder(mService).setTask(task).build(); + + doReturn(true).when(mRootActivityContainer).resumeHomeActivity(any(), any(), anyInt()); + + mService.setBooted(true); + + // Trigger resume on all displays + mRootActivityContainer.resumeFocusedStacksTopActivities(); + + // Verify that home activity was started on the default display + verify(mRootActivityContainer).resumeHomeActivity(any(), any(), eq(DEFAULT_DISPLAY)); + } + /** * Verify that a lingering transition is being executed in case the activity to be resumed is * already resumed -- cgit v1.2.3-59-g8ed1b