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

Commit 3ff5a0b4 authored by Andrii Kulian's avatar Andrii Kulian
Browse files

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
parent 0bcd6984
Loading
Loading
Loading
Loading
+24 −12
Original line number Diff line number Diff line
@@ -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) {
    /**
     * 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 */);
                // 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);
    }

+54 −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_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