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

Commit 57cce33e authored by Merissa Mitchell's avatar Merissa Mitchell
Browse files

[PiP on Desktop] Fix multi-activity PiP expand

Recall: http://recall/clips/01dd5aa4-2628-45a7-852e-652faeb5bef0

With multi-activity PiP (e.g. Netflix), the PiP task is reparented to
the parent task when PiP is expanded. This means that the PiP will be
restored to the parent's windowing mode regardless of what windowing
mode we set the PiP task to. This led to mismatched windowing mode in
the following cases due to the parent retaining the pre-PiP windowing
mode:
- PiP from fullscreen, enter Desktop Windowing and expand PiP -> PiP
  restores to fullscreen
- PiP from freeform, exit Desktop Windowing and expand PiP -> PiP
  restores to freeform

This CL adds a change to the parent task's windowing mode and bounds to
the expand PiP transition based on whether Desktop session is active at
the time of the transition.

Bug: 377707798
Test: atest DesktopPipTransitionControllerTest PipDesktopStateTest PipSchedulerTest
Flag: com.android.window.flags.enable_desktop_windowing_pip

Change-Id: Ie0bf6656bbf5985aab0e6d4e4f748ff81bb8af8d
parent 4ffe9032
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import com.android.wm.shell.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.desktopmode.DesktopUserRepositories
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler
import java.util.Optional
@@ -93,4 +94,7 @@ class PipDesktopState(
    /** Returns whether there is a drag-to-desktop transition in progress. */
    fun isDragToDesktopInProgress(): Boolean =
        isDesktopWindowingPipEnabled() && dragToDesktopTransitionHandlerOptional.get().inProgress

    /** Returns the DisplayLayout associated with the display where PiP window is in. */
    fun getCurrentDisplayLayout(): DisplayLayout = pipDisplayLayoutState.displayLayout
}
+7 −3
Original line number Diff line number Diff line
@@ -153,10 +153,12 @@ public abstract class Pip2Module {
            @ShellMainThread ShellExecutor mainExecutor,
            PipTransitionState pipTransitionState,
            Optional<SplitScreenController> splitScreenControllerOptional,
            Optional<DesktopPipTransitionController> desktopPipTransitionController,
            PipDesktopState pipDesktopState,
            DisplayController displayController) {
        return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
                splitScreenControllerOptional, pipDesktopState, displayController);
                splitScreenControllerOptional, desktopPipTransitionController, pipDesktopState,
                displayController);
    }

    @WMSingleton
@@ -259,14 +261,16 @@ public abstract class Pip2Module {
    @WMSingleton
    @Provides
    static Optional<DesktopPipTransitionController> provideDesktopPipTransitionController(
            Context context, Optional<DesktopTasksController> desktopTasksControllerOptional,
            Context context, ShellTaskOrganizer shellTaskOrganizer,
            Optional<DesktopTasksController> desktopTasksControllerOptional,
            Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
            PipDesktopState pipDesktopState
    ) {
        if (DesktopModeStatus.canEnterDesktopMode(context)
                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()) {
            return Optional.of(
                    new DesktopPipTransitionController(desktopTasksControllerOptional.get(),
                    new DesktopPipTransitionController(shellTaskOrganizer,
                            desktopTasksControllerOptional.get(),
                            desktopUserRepositoriesOptional.get(), pipDesktopState));
        }
        return Optional.empty();
+53 −3
Original line number Diff line number Diff line
@@ -17,21 +17,71 @@
package com.android.wm.shell.desktopmode

import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.graphics.Rect
import android.os.IBinder
import android.window.DesktopExperienceFlags
import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.pip.PipDesktopState
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE

/**
 * Controller to perform extra handling to PiP transitions that are entering while in Desktop mode.
 */
/** Controller to perform extra handling to PiP transitions while in Desktop mode. */
class DesktopPipTransitionController(
    private val shellTaskOrganizer: ShellTaskOrganizer,
    private val desktopTasksController: DesktopTasksController,
    private val desktopUserRepositories: DesktopUserRepositories,
    private val pipDesktopState: PipDesktopState,
) {
    /**
     * This is called by [PipScheduler#getExitPipViaExpandTransaction] before starting a PiP
     * transition. In the case of multi-activity PiP, we might need to update the parent task's
     * windowing mode and bounds based on whether we are in Desktop Windowing.
     *
     * @param wct WindowContainerTransaction that will apply these changes
     * @param parentTaskId id taken from TaskInfo#lastParentTaskIdBeforePip
     */
    fun maybeUpdateParentInWct(wct: WindowContainerTransaction, parentTaskId: Int) {
        if (!pipDesktopState.isDesktopWindowingPipEnabled()) {
            return
        }

        if (parentTaskId == ActivityTaskManager.INVALID_TASK_ID) {
            logD("maybeUpdateParentInWct: Task is not multi-activity PiP")
            return
        }

        val parentTask = shellTaskOrganizer.getRunningTaskInfo(parentTaskId)
        if (parentTask == null) {
            logW(
                "maybeUpdateParentInWct: Failed to find RunningTaskInfo for parentTaskId %d",
                parentTaskId,
            )
            return
        }

        val defaultFreeformBounds =
            if (parentTask.lastNonFullscreenBounds.isEmpty) {
                calculateDefaultDesktopTaskBounds(pipDesktopState.getCurrentDisplayLayout())
            } else {
                parentTask.lastNonFullscreenBounds
            }

        val newResolvedWinMode =
            if (pipDesktopState.isPipInDesktopMode()) WINDOWING_MODE_FREEFORM
            else WINDOWING_MODE_FULLSCREEN

        if (newResolvedWinMode != parentTask.windowingMode) {
            wct.setWindowingMode(parentTask.token, newResolvedWinMode)
            wct.setBounds(
                parentTask.token,
                if (newResolvedWinMode == WINDOWING_MODE_FREEFORM) defaultFreeformBounds else Rect()
            )
        }
    }

    /**
     * This is called by [PipTransition#handleRequest] when a request for entering PiP is received.
+10 −2
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.desktopmode.DesktopPipTransitionController;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -72,6 +73,7 @@ public class PipScheduler implements PipTransitionState.PipTransitionStateChange
    private final PipTransitionState mPipTransitionState;
    private final DisplayController mDisplayController;
    private final PipDesktopState mPipDesktopState;
    private final Optional<DesktopPipTransitionController> mDesktopPipTransitionController;
    private final Optional<SplitScreenController> mSplitScreenControllerOptional;
    private PipTransitionController mPipTransitionController;
    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
@@ -89,6 +91,7 @@ public class PipScheduler implements PipTransitionState.PipTransitionStateChange
            ShellExecutor mainExecutor,
            PipTransitionState pipTransitionState,
            Optional<SplitScreenController> splitScreenControllerOptional,
            Optional<DesktopPipTransitionController> desktopPipTransitionController,
            PipDesktopState pipDesktopState,
            DisplayController displayController) {
        mContext = context;
@@ -97,6 +100,7 @@ public class PipScheduler implements PipTransitionState.PipTransitionStateChange
        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this);
        mPipDesktopState = pipDesktopState;
        mDesktopPipTransitionController = desktopPipTransitionController;
        mSplitScreenControllerOptional = splitScreenControllerOptional;
        mDisplayController = displayController;
        mSurfaceControlTransactionFactory =
@@ -118,9 +122,13 @@ public class PipScheduler implements PipTransitionState.PipTransitionStateChange
        WindowContainerTransaction wct = new WindowContainerTransaction();
        // final expanded bounds to be inherited from the parent
        wct.setBounds(pipTaskToken, null);
        // if we are hitting a multi-activity case
        // windowing mode change will reparent to original host task
        wct.setWindowingMode(pipTaskToken, mPipDesktopState.getOutPipWindowingMode());

        // In multi-activity case, windowing mode change will reparent to original host task, so we
        // have to update the parent windowing mode to what is expected.
        mDesktopPipTransitionController.ifPresent(c -> c.maybeUpdateParentInWct(wct,
                mPipTransitionState.getPipTaskInfo().lastParentTaskIdBeforePip));

        return wct;
    }

+55 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.wm.shell.pip2.phone.transition;

import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.Surface.ROTATION_0;

import static com.android.wm.shell.pip2.phone.transition.PipTransitionUtils.getChangeByToken;
@@ -191,13 +193,23 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
        PipExpandAnimator animator = mPipExpandAnimatorSupplier.get(mContext, pipLeash,
                startTransaction, finishTransaction, endBounds, startBounds, endBounds,
                sourceRectHint, delta, mPipDesktopState.isPipInDesktopMode());
        animator.setAnimationStartCallback(() -> mPipInteractionHandler.begin(pipLeash,
                PipInteractionHandler.INTERACTION_EXIT_PIP));
        animator.setAnimationStartCallback(() -> {
            mPipInteractionHandler.begin(pipLeash, PipInteractionHandler.INTERACTION_EXIT_PIP);

            if (parentBeforePip != null) {
                setupMultiActivityExpandAnimation(info, startTransaction, pipLeash,
                        parentBeforePip);
            }
        });

        final TransitionInfo.Change finalPipChange = pipChange;
        animator.setAnimationEndCallback(() -> {
            if (parentBeforePip != null) {
                // TODO b/377362511: Animate local leash instead to also handle letterbox case.
                // For multi-activity, set the crop to be null
                finishTransaction.setCrop(pipLeash, null);
                setupMultiActivityAnimationFinalState(finishTransaction, finalPipChange, pipLeash,
                        parentBeforePip);
            }
            finishTransition();
            mPipInteractionHandler.end();
@@ -207,6 +219,47 @@ public class PipExpandHandler implements Transitions.TransitionHandler {
        return true;
    }

    private void setupMultiActivityExpandAnimation(@NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl pipLeash,
            @NonNull TransitionInfo.Change parentBeforePip) {
        if (!mPipDesktopState.isDesktopWindowingPipEnabled()) {
            return;
        }

        final int rootIndex = info.findRootIndex(mPipDisplayLayoutState.getDisplayId());
        final int parentWindowingMode = parentBeforePip.getTaskInfo().getWindowingMode();
        if (rootIndex != -1 && parentWindowingMode == WINDOWING_MODE_FREEFORM) {
            // Reparent PiP activity to the root leash if it's animating to freeform so that it is
            // not cropped by the parent task.
            SurfaceControl rootLeash = info.getRoot(rootIndex).getLeash();
            startTransaction.reparent(pipLeash, rootLeash);
            startTransaction.setAlpha(parentBeforePip.getLeash(), 0);
        } else if (parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
            // Don't animate the parent task; show it immediately when the PiP animation finishes
            parentBeforePip.setStartAbsBounds(parentBeforePip.getEndAbsBounds());
            startTransaction.setPosition(parentBeforePip.getLeash(),
                    parentBeforePip.getStartAbsBounds().left,
                    parentBeforePip.getStartAbsBounds().top);
            startTransaction.setCrop(parentBeforePip.getLeash(), parentBeforePip.getEndAbsBounds());
        }
    }

    private void setupMultiActivityAnimationFinalState(
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull TransitionInfo.Change pipChange, @NonNull SurfaceControl pipLeash,
            @NonNull TransitionInfo.Change parentBeforePip) {
        if (!mPipDesktopState.isDesktopWindowingPipEnabled()
                || parentBeforePip.getTaskInfo().getWindowingMode() != WINDOWING_MODE_FREEFORM) {
            return;
        }

        // Reparent the PiP activity to the parent task and reset its position
        finishTransaction.reparent(pipLeash, parentBeforePip.getLeash());
        finishTransaction.setPosition(pipLeash, pipChange.getEndRelOffset().x,
                pipChange.getEndRelOffset().y);
        finishTransaction.setAlpha(parentBeforePip.getLeash(), 1);
    }

    private boolean startExpandToSplitAnimation(@NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
Loading