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

Commit fd4a0500 authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Fix unused new stack when bring existing activity to front

If a non-top reusable activity is found when starting activity on
secondary display, the condition in
ActivityStarter.setTargetStackAndMoveToFrontIfNeeded will get launch
stack by the reusable activity. But it actually creates a new stack
with the target display which won't be used and seems as a leakage.
That may also disturb focused stack checking when it is on the top of
display (e.g. other tasks are moved to back).

This change gives the preference to use the stack of candidate task
if it already exists on target display. Then it will have the same
behavior as default display that reuses the existing stack.

Bug: 117799939
Test: atest ActivityStarterTests#testBringTaskToFrontOnSecondaryDisplay
Change-Id: I65213386d9802f39ac7ba0d377a06711913c672d
parent a1b9c31c
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ import static android.content.res.Configuration.UI_MODE_TYPE_VR_HEADSET;
import static android.os.Build.VERSION_CODES.HONEYCOMB;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Process.SYSTEM_UID;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;

import static com.android.server.am.ActivityTaskManagerDebugConfig.DEBUG_CONFIGURATION;
@@ -2211,12 +2212,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo
    }

    /**
     * @return display id to which this record is attached, -1 if not attached.
     * @return display id to which this record is attached,
     *         {@link android.view.Display#INVALID_DISPLAY} if not attached.
     */
    int getDisplayId() {
        final ActivityStack stack = getStack();
        if (stack == null) {
            return -1;
            return INVALID_DISPLAY;
        }
        return stack.mDisplayId;
    }
+15 −2
Original line number Diff line number Diff line
@@ -2457,7 +2457,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
        }
        if (displayId != INVALID_DISPLAY && canLaunchOnDisplay(r, displayId)) {
            if (r != null) {
                stack = (T) getValidLaunchStackOnDisplay(displayId, r, options);
                stack = (T) getValidLaunchStackOnDisplay(displayId, r, candidateTask, options);
                if (stack != null) {
                    return stack;
                }
@@ -2522,10 +2522,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
     * If there is no such stack, new dynamic stack can be created.
     * @param displayId Target display.
     * @param r Activity that should be launched there.
     * @param candidateTask The possible task the activity might be put in.
     * @return Existing stack if there is a valid one, new dynamic stack if it is valid or null.
     */
    ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
            @Nullable ActivityOptions options) {
            @Nullable TaskRecord candidateTask, @Nullable ActivityOptions options) {
        final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId);
        if (activityDisplay == null) {
            throw new IllegalArgumentException(
@@ -2536,6 +2537,13 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
            return null;
        }

        // If {@code r} is already in target display and its task is the same as the candidate task,
        // the intention should be getting a launch stack for the reusable activity, so we can use
        // the existing stack.
        if (r.getDisplayId() == displayId && r.getTask() == candidateTask) {
            return candidateTask.getStack();
        }

        // Return the topmost valid stack on the display.
        for (int i = activityDisplay.getChildCount() - 1; i >= 0; --i) {
            final ActivityStack stack = activityDisplay.getChildAt(i);
@@ -2556,6 +2564,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D
        return null;
    }

    ActivityStack getValidLaunchStackOnDisplay(int displayId, @NonNull ActivityRecord r,
            @Nullable ActivityOptions options) {
        return getValidLaunchStackOnDisplay(displayId, r, null /* candidateTask */, options);
    }

    // TODO: Can probably be consolidated into getLaunchStack()...
    private boolean isValidLaunchStack(ActivityStack stack, int displayId, ActivityRecord r) {
        switch (stack.getActivityType()) {
+57 −13
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;

import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
import static com.android.server.am.ActivityDisplay.POSITION_TOP;
import static com.android.server.am.ActivityTaskManagerService.ANIMATE;

import static org.junit.Assert.assertEquals;
@@ -562,23 +563,13 @@ public class ActivityStarterTests extends ActivityTestsBase {
                false /* mockGetLaunchStack */);

        // Create a secondary display at bottom.
        final TestActivityDisplay secondaryDisplay = spy(addNewActivityDisplayAt(POSITION_BOTTOM));
        final TestActivityDisplay secondaryDisplay = spy(createNewActivityDisplay());
        mSupervisor.addChild(secondaryDisplay, POSITION_BOTTOM);
        final ActivityStack stack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
                ACTIVITY_TYPE_STANDARD, true /* onTop */);

        // Create an activity record on the top of secondary display.
        final ComponentName componentName = ComponentName.createRelative(
                DEFAULT_COMPONENT_PACKAGE_NAME,
                DEFAULT_COMPONENT_PACKAGE_NAME + ".ReusableActivity");
        final TaskRecord taskRecord = new TaskBuilder(mSupervisor)
                .setComponent(componentName)
                .setStack(stack)
                .build();
        final ActivityRecord topActivityOnSecondaryDisplay = new ActivityBuilder(mService)
                .setComponent(componentName)
                .setLaunchMode(LAUNCH_SINGLE_TASK)
                .setTask(taskRecord)
                .build();
        final ActivityRecord topActivityOnSecondaryDisplay = createSingleTaskActivityOn(stack);

        // Put an activity on default display as the top focused activity.
        new ActivityBuilder(mService).setCreateTask(true).build();
@@ -599,6 +590,59 @@ public class ActivityStarterTests extends ActivityTestsBase {
        verify(secondaryDisplay, times(1)).createStack(anyInt(), anyInt(), anyBoolean());
    }

    /**
     * This test ensures that when starting an existing non-top single task activity on secondary
     * display which is the top focused display, it should bring the task to front without creating
     * unused stack.
     */
    @Test
    public void testBringTaskToFrontOnSecondaryDisplay() {
        final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
                false /* mockGetLaunchStack */);

        // Create a secondary display with an activity.
        final TestActivityDisplay secondaryDisplay = spy(createNewActivityDisplay());
        mSupervisor.addChild(secondaryDisplay, POSITION_TOP);
        final ActivityRecord singleTaskActivity = createSingleTaskActivityOn(
                secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
                        ACTIVITY_TYPE_STANDARD, false /* onTop */));

        // Create another activity on top of the secondary display.
        final ActivityStack topStack = secondaryDisplay.createStack(WINDOWING_MODE_FULLSCREEN,
                ACTIVITY_TYPE_STANDARD, true /* onTop */);
        final TaskRecord topTask = new TaskBuilder(mSupervisor).setStack(topStack).build();
        new ActivityBuilder(mService).setTask(topTask).build();

        // Start activity with the same intent as {@code singleTaskActivity} on secondary display.
        final ActivityOptions options = ActivityOptions.makeBasic()
                .setLaunchDisplayId(secondaryDisplay.mDisplayId);
        final int result = starter.setReason("testBringTaskToFrontOnSecondaryDisplay")
                .setIntent(singleTaskActivity.intent)
                .setActivityOptions(options.toBundle())
                .execute();

        // Ensure result is moving existing task to front.
        assertEquals(START_TASK_TO_FRONT, result);

        // Ensure secondary display only creates two stacks.
        verify(secondaryDisplay, times(2)).createStack(anyInt(), anyInt(), anyBoolean());
    }

    private ActivityRecord createSingleTaskActivityOn(ActivityStack stack) {
        final ComponentName componentName = ComponentName.createRelative(
                DEFAULT_COMPONENT_PACKAGE_NAME,
                DEFAULT_COMPONENT_PACKAGE_NAME + ".SingleTaskActivity");
        final TaskRecord taskRecord = new TaskBuilder(mSupervisor)
                .setComponent(componentName)
                .setStack(stack)
                .build();
        return new ActivityBuilder(mService)
                .setComponent(componentName)
                .setLaunchMode(LAUNCH_SINGLE_TASK)
                .setTask(taskRecord)
                .build();
    }

    /**
     * This test ensures that a reused top activity in the top focused stack is able to be
     * reparented to another display.