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

Commit de420404 authored by Merissa Mitchell's avatar Merissa Mitchell
Browse files

[PiP on CD] Only allow 1 PiP across multiple displays.

When a PiP transition is started, Core checks whether there is an
existing PiP and remove it when necessary in
RootWindowContainer#moveActivityToPinnedRootTaskInner.

Previously, the code only checks for an existing PiP in the current
TaskDisplayArea, but for PiP in Connected Displays, we want to check
across displays to ensure we only have 1 PiP. This CL calls
removeRootTasksInWindowingMode(WINDOWING_MODE_PINNED) directly if the
Pip2 flag is enabled.

Bug: 397769346
Test: atest PipDesktopStateTest
Test: Manual - start PiP on first display, then start PiP on second
display. Verify first PiP is removed, and second PiP is WAI
Test: Manual - start PiP, then back navigation on second app to start
PiP on the same display. Verify first PiP is removed, and second PiP is
WAI
Flag: com.android.window.flags.enable_connected_displays_pip

Change-Id: I348c632c0e86447eb4628dfee15efa21dd4b5a12
parent b9fbfa2c
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -45,10 +45,12 @@ class PipDesktopState(

    /**
     * Returns whether PiP in Connected Displays is enabled by checking the following:
     * - PiP in Desktop Windowing is enabled
     * - PiP in Connected Displays flag is enabled
     * - PiP2 flag is enabled
     */
    fun isConnectedDisplaysPipEnabled(): Boolean =
        isDesktopWindowingPipEnabled() &&
                DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue && Flags.enablePip2()

    /** Returns whether the display with the PiP task is in freeform windowing mode. */
+46 −0
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;

import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ComponentUtils;
@@ -80,6 +81,7 @@ import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
import com.android.wm.shell.pip2.phone.transition.PipExpandHandler;
import com.android.wm.shell.pip2.phone.transition.PipTransitionUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransitionUtil;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.ShellInit;
@@ -299,6 +301,10 @@ public class PipTransition extends PipTransitionController implements
            // playing PiP transitions, so reset those transforms if needed.
            prepareOtherTargetTransforms(info, startTransaction, finishTransaction);

            // This PiP transition might have caused a previous PiP to be dismissed. If so, we need
            // to clean up the PiP state.
            cleanUpPrevPipIfPresent(info, startTransaction, finishTransaction);

            // Update the PipTransitionState while supplying the PiP leash and token to be cached.
            Bundle extra = new Bundle();
            extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
@@ -929,6 +935,46 @@ public class PipTransition extends PipTransitionController implements
        }
    }

    /**
     * This is called by [startAnimation] when a enter PiP transition is received, and before
     * mPipTransitionState is updated with the incoming PiP task info. If a change is found
     * for the previous PiP with change TO_BACK, the previous PiP was dismissed by Core. We want to
     * update the state in PipTransitionState so everything is cleaned up and also ensure the
     * previous PiP is no longer visible.
     */
    private void cleanUpPrevPipIfPresent(@NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTx,
            @NonNull SurfaceControl.Transaction finishTx) {
        TransitionInfo.Change previousPipChange = null;
        TaskInfo previousPipTaskInfo = mPipTransitionState.getPipTaskInfo();
        if (previousPipTaskInfo == null) {
            return;
        }

        for (TransitionInfo.Change change : info.getChanges()) {
            if (change.getTaskInfo() != null
                    && change.getTaskInfo().getTaskId() == previousPipTaskInfo.getTaskId()
                    && TransitionUtil.isClosingMode(change.getMode())) {
                previousPipChange = change;
                break;
            }
        }

        if (previousPipChange == null) {
            return;
        }

        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "cleanUpPrevPipIfPresent: Previous PiP with taskId=%d found with closing mode, "
                        + "clean up PiP state",
                previousPipTaskInfo.getTaskId());

        mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
        mPipTransitionState.setState(PipTransitionState.EXITED_PIP);
        startTx.setAlpha(previousPipChange.getLeash(), 0);
        finishTx.setAlpha(previousPipChange.getLeash(), 0);
    }

    /**
     * Sets the type of animation to run upon entering PiP.
     *
+10 −6
Original line number Diff line number Diff line
@@ -2067,13 +2067,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
        try {
            // This will change the root pinned task's windowing mode to its original mode, ensuring
            // we only have one root task that is in pinned mode.
            final Task rootPinnedTask = taskDisplayArea.getRootPinnedTask();
            if (rootPinnedTask != null) {
                transitionController.collect(rootPinnedTask);
            // The new ActivityRecord should replace the existing PiP, so it's more desirable
            // that the old PiP disappears instead of turning to full-screen at the same time,
            // as the Task#dismissPip is trying to do.
            if (ActivityTaskManagerService.isPip2ExperimentEnabled()) {
                removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
            } else {
                final Task rootPinnedTask = taskDisplayArea.getRootPinnedTask();
                if (rootPinnedTask != null) {
                    transitionController.collect(rootPinnedTask);
                    removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
                }
            }

            transitionController.collect(task);