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

Commit 6e054b4f authored by Ben Lin's avatar Ben Lin
Browse files

Run transitions on multi-activity PIP when moved to back.

When a multi-activity PIP-able task gets moved to the back, the top
activity gets paused and then enters PIP if the app allows it. However,
since the original activity below the PIP activity was not visible and
thus stopped, its visibility doesn't change. Without the visibility
changing, we don't run any TRANSIT_TASK_TO_BACK transitions on it as we
should.

This CL does 2 things:

1) When moving task to back, always run EnsureVisibilityHelper to check
visibility regardless of whether the new top activity is resumed or not.
This is because in the process of resuming the new top activity, there
are checks for visibility which isn't yet updated. We should just update
the visibility first.

2) When entering PIP and there are more than one activity and there is a
TRANSIT_TASK_TO_BACK transition (meaning a moveTaskToBack operation was
done), add the old activity to the displayContent's list of closing apps
and add a new flag to ensure the transition is ran even though the
activity's visibility did not change.

Bug: 159376160
Test: Minimize a task with multiple activity (e.g. VLC, Netflix), and
the video activity enters PIP correctly, with the back transition
correctly ran on the original task
Test: atest
com.android.server.wm.RootActivityContainerTests#testMovingBottomMostStackActivityToPinnedStack
Test: atest
com.android.server.wm.AppTransitionControllerTest#testGetAnimationTargets_visibilityAlreadyUpdated_butForcedTransitionRequested

Change-Id: I5de59a6b8e1b244a0df896dd61c5b4b103c3fd7f
parent b49d14d2
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -658,6 +658,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A

    // TODO: Have a WindowContainer state for tracking exiting/deferred removal.
    boolean mIsExiting;
    // Force an app transition to be ran in the case the visibility of the app did not change.
    // We use this for the case of moving a Root Task to the back with multiple activities, and the
    // top activity enters PIP; the bottom activity's visibility stays the same, but we need to
    // run the transition.
    boolean mRequestForceTransition;

    boolean mEnteringAnimation;

@@ -4199,6 +4204,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        if (mUseTransferredAnimation) {
            return false;
        }
        // If it was set to true, reset the last request to force the transition.
        mRequestForceTransition = false;
        return super.applyAnimation(lp, transit, enter, isVoiceInteraction, sources);
    }

@@ -4342,7 +4349,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
        // transition animation
        // * or this is an opening app and windows are being replaced (e.g. freeform window to
        //   normal window).
        return isVisible() != visible || (!isVisible() && mIsExiting)
        return isVisible() != visible || mRequestForceTransition || (!isVisible() && mIsExiting)
                || (visible && forAllWindows(WindowState::waitingForReplacement, true));
    }

+4 −6
Original line number Diff line number Diff line
@@ -2489,15 +2489,13 @@ class ActivityStack extends Task {
            return true;
        }

        ActivityRecord topActivity = getDisplayArea().topRunningActivity();
        ActivityStack topStack = topActivity.getRootTask();
        if (topStack != null && topStack != this && topActivity.isState(RESUMED)) {
            // The new top activity is already resumed, so there's a good chance that nothing will
            // get resumed below. So, update visibility now in case the transition is closed
            // prematurely.
        mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
                getDisplay().mDisplayId, false /* markFrozenIfConfigChanged */,
                false /* deferResume */);

        ActivityRecord topActivity = getDisplayArea().topRunningActivity();
        ActivityStack topStack = topActivity.getRootTask();
        if (topStack != null && topStack != this && topActivity.isState(RESUMED)) {
            // Usually resuming a top activity triggers the next app transition, but nothing's got
            // resumed in this case, so we need to execute it explicitly.
            getDisplay().mDisplayContent.executeAppTransition();
+15 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;

import static com.android.server.policy.PhoneWindowManager.SYSTEM_DIALOG_REASON_ASSIST;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -2156,6 +2157,20 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
                // On the other hand, ActivityRecord#onParentChanged takes care of setting the
                // up-to-dated pinned stack information on this newly created stack.
                r.reparent(stack, MAX_VALUE, reason);

                // In the case of this activity entering PIP due to it being moved to the back,
                // the old activity would have a TRANSIT_TASK_TO_BACK transition that needs to be
                // ran. But, since its visibility did not change (note how it was STOPPED/not
                // visible, and with it now at the back stack, it remains not visible), the logic to
                // add the transition is automatically skipped. We then add this activity manually
                // to the list of apps being closed, and request its transition to be ran.
                final ActivityRecord oldTopActivity = task.getTopMostActivity();
                if (oldTopActivity != null && oldTopActivity.isState(STOPPED)
                        && task.getDisplayContent().mAppTransition.getAppTransition()
                                == TRANSIT_TASK_TO_BACK) {
                    task.getDisplayContent().mClosingApps.add(oldTopActivity);
                    oldTopActivity.mRequestForceTransition = true;
                }
            }
            // The intermediate windowing mode to be set on the ActivityRecord later.
            // This needs to happen before the re-parenting, otherwise we will always set the
+34 −0
Original line number Diff line number Diff line
@@ -198,6 +198,40 @@ public class AppTransitionControllerTest extends WindowTestsBase {
                        opening, closing, false /* visible */));
    }

    @Test
    public void testGetAnimationTargets_visibilityAlreadyUpdated_butForcedTransitionRequested() {
        // [DisplayContent] -+- [TaskStack1] - [Task1] - [ActivityRecord1] (closing, invisible)
        //                   +- [TaskStack2] - [Task2] - [ActivityRecord2] (opening, visible)
        final ActivityStack stack1 = createTaskStackOnDisplay(mDisplayContent);
        final ActivityRecord activity1 = WindowTestUtils.createTestActivityRecord(stack1);
        activity1.setVisible(true);
        activity1.mVisibleRequested = true;
        activity1.mRequestForceTransition = true;

        final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
        final ActivityRecord activity2 = WindowTestUtils.createTestActivityRecord(stack2);
        activity2.setVisible(false);
        activity2.mVisibleRequested = false;
        activity2.mRequestForceTransition = true;

        final ArraySet<ActivityRecord> opening = new ArraySet<>();
        opening.add(activity1);
        final ArraySet<ActivityRecord> closing = new ArraySet<>();
        closing.add(activity2);

        // The visibility are already updated, but since forced transition is requested, it will
        // be included.
        WindowManagerService.sHierarchicalAnimations = false;
        assertEquals(
                new ArraySet<>(new WindowContainer[]{activity1}),
                AppTransitionController.getAnimationTargets(
                        opening, closing, true /* visible */));
        assertEquals(
                new ArraySet<>(new WindowContainer[]{activity2}),
                AppTransitionController.getAnimationTargets(
                        opening, closing, false /* visible */));
    }

    @Test
    public void testGetAnimationTargets_exitingBeforeTransition() {
        // Create another non-empty task so the animation target won't promote to task display area.
+24 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
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.ActivityStack.ActivityState.STOPPED;
import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;

@@ -148,6 +149,29 @@ public class RootActivityContainerTests extends ActivityTestsBase {
        ensureStackPlacement(mFullscreenStack, firstActivity);
    }

    @Test
    public void testMovingBottomMostStackActivityToPinnedStack() {
        final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true)
                .setStack(mFullscreenStack).build();
        final Task task = firstActivity.getTask();

        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(task)
                .setStack(mFullscreenStack).build();

        mFullscreenStack.moveTaskToBack(task);

        // Ensure full screen stack has both tasks.
        ensureStackPlacement(mFullscreenStack, firstActivity, secondActivity);
        assertEquals(task.getTopMostActivity(), secondActivity);
        firstActivity.setState(STOPPED, "testMovingBottomMostStackActivityToPinnedStack");


        // Move first activity to pinned stack.
        mRootWindowContainer.moveActivityToPinnedStack(secondActivity, "initialMove");

        assertTrue(firstActivity.mRequestForceTransition);
    }

    private static void ensureStackPlacement(ActivityStack stack, ActivityRecord... activities) {
        final Task task = stack.getBottomMostTask();
        final ArrayList<ActivityRecord> stackActivities = new ArrayList<>();