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

Commit d551936c authored by Vania Desmonda's avatar Vania Desmonda
Browse files

[Pip-CD] (2/n) Create and position mirror for each connected display on PiP drag move.

Demo: https://drive.google.com/file/d/1GW44ZSb7vYU2W-NozVL2_tISkOsNgWOm/view?usp=sharing

Test: atest
Flag: com.android.window.flags.enable_dragging_pip_across_displays
Bug: 383403514
Change-Id: I983858611872438f2958ac553fa6e561f3ec03f6
parent a937c8d2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ object MultiDisplayDragMoveBoundsCalculator {
     * @param y The current y-coordinate of the drag pointer (in pixels).
     * @return A RectF object representing the calculated global DP bounds of the window.
     */
    @JvmStatic
    fun calculateGlobalDpBoundsForDrag(
        startDisplayLayout: DisplayLayout,
        repositionStartPoint: PointF,
@@ -73,6 +74,7 @@ object MultiDisplayDragMoveBoundsCalculator {
     * @param displayLayout The DisplayLayout representing the display to convert the bounds to.
     * @return A Rect object representing the local pixel bounds on the specified display.
     */
    @JvmStatic
    fun convertGlobalDpToLocalPxForRect(rectDp: RectF, displayLayout: DisplayLayout): Rect {
        val leftTopPxDisplay = displayLayout.globalDpToLocalPx(rectDp.left, rectDp.top)
        val rightBottomPxDisplay = displayLayout.globalDpToLocalPx(rectDp.right, rectDp.bottom)
+10 −0
Original line number Diff line number Diff line
@@ -54,6 +54,16 @@ class PipDesktopState(
        isDesktopWindowingPipEnabled() &&
                DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue && Flags.enablePip2()

    /**
     * Returns whether dragging PiP in Connected Displays is enabled by checking the following:
     * - Dragging PiP in Connected Displays flag is enabled
     * - PiP in Connected Displays flag is enabled
     * - PiP2 flag is enabled
     */
    fun isDraggingPipAcrossDisplaysEnabled(): Boolean =
        DesktopExperienceFlags.ENABLE_DRAGGING_PIP_ACROSS_DISPLAYS.isTrue &&
                isConnectedDisplaysPipEnabled()

    /** Returns whether the display with the PiP task is in freeform windowing mode. */
    private fun isDisplayInFreeform(): Boolean {
        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+15 −2
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipDisplayTransferHandler;
import com.android.wm.shell.pip2.phone.PipInteractionHandler;
import com.android.wm.shell.pip2.phone.PipMotionHelper;
import com.android.wm.shell.pip2.phone.PipScheduler;
@@ -196,12 +197,24 @@ public abstract class Pip2Module {
            FloatingContentCoordinator floatingContentCoordinator,
            PipUiEventLogger pipUiEventLogger,
            @ShellMainThread ShellExecutor mainExecutor,
            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
            Optional<PipPerfHintController> pipPerfHintControllerOptional,
            PipDisplayTransferHandler pipDisplayTransferHandler) {
        return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController,
                pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler,
                sizeSpecSource, pipDisplayLayoutState, pipDesktopState, displayController,
                pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, mainExecutor,
                pipPerfHintControllerOptional);
                pipPerfHintControllerOptional, pipDisplayTransferHandler);
    }

    @WMSingleton
    @Provides
    static PipDisplayTransferHandler providePipDisplayTransferHandler(Context context,
            PipTransitionState pipTransitionState,
            PipScheduler pipScheduler, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            PipBoundsState pipBoundsState, DisplayController displayController
    ) {
        return new PipDisplayTransferHandler(context, pipTransitionState, pipScheduler,
                rootTaskDisplayAreaOrganizer, pipBoundsState, displayController);
    }

    @WMSingleton
+124 −10
Original line number Diff line number Diff line
@@ -15,14 +15,29 @@
 */
package com.android.wm.shell.pip2.phone;

import android.annotation.NonNull;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE;

import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.util.ArrayMap;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.MultiDisplayDragMoveBoundsCalculator;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;

/**
 * Handler for moving PiP window to another display when the device is connected to external
 * display(s) in extended mode.
@@ -34,15 +49,31 @@ public class PipDisplayTransferHandler implements
    static final String ORIGIN_DISPLAY_ID_KEY = "origin_display_id";
    static final String TARGET_DISPLAY_ID_KEY = "target_display_id";

    @NonNull private final PipTransitionState mPipTransitionState;
    @NonNull private final PipScheduler mPipScheduler;
    private final PipBoundsState mPipBoundsState;
    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
            mSurfaceControlTransactionFactory;
    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
    private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
    private final DisplayController mDisplayController;
    private final PipTransitionState mPipTransitionState;
    private final PipScheduler mPipScheduler;

    @VisibleForTesting boolean mWaitingForDisplayTransfer;
    @VisibleForTesting
    ArrayMap<Integer, SurfaceControl> mOnDragMirrorPerDisplayId = new ArrayMap<>();

    public PipDisplayTransferHandler(PipTransitionState pipTransitionState,
            PipScheduler pipScheduler) {
    public PipDisplayTransferHandler(Context context, PipTransitionState pipTransitionState,
            PipScheduler pipScheduler, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            PipBoundsState pipBoundsState, DisplayController displayController) {
        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
        mPipTransitionState.addPipTransitionStateChangedListener(this);
        mPipScheduler = pipScheduler;
        mSurfaceControlTransactionFactory =
                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
        mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(context);
        mPipBoundsState = pipBoundsState;
        mDisplayController = displayController;
    }

    void scheduleMovePipToDisplay(int originDisplayId, int targetDisplayId) {
@@ -72,8 +103,8 @@ public class PipDisplayTransferHandler implements
                    break;
                }

                final SurfaceControl.Transaction startTx = extra.getParcelable(
                        PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
                final Transaction startTx = extra.getParcelable(
                        PipTransition.PIP_START_TX, Transaction.class);
                final Rect destinationBounds = extra.getParcelable(
                        PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);

@@ -81,11 +112,94 @@ public class PipDisplayTransferHandler implements
        }
    }

    private void startMoveToDisplayAnimation(SurfaceControl.Transaction startTx,
            Rect destinationBounds) {
    private void startMoveToDisplayAnimation(Transaction startTx, Rect destinationBounds) {
        if (startTx == null) return;

        startTx.apply();
        mPipScheduler.scheduleFinishPipBoundsChange(destinationBounds);
    }

    /**
     * Show a drag indicator mirror on each connected display according to the current pointer
     * position.
     *
     * @param originDisplayId           the display ID where the drag originated from
     * @param currentDisplayId          the current display ID the pointer is on
     * @param originPointerCoordinates  the position of the pointer when it started dragging
     * @param currentPointerCoordinates the position of the pointer it's currently on
     * @param originBounds              the PiP bounds when dragging starts
     */
    public void showDragMirrorOnConnectedDisplays(int originDisplayId, int currentDisplayId,
            PointF originPointerCoordinates, PointF currentPointerCoordinates, Rect originBounds) {
        DisplayLayout originDisplayLayout = mDisplayController.getDisplayLayout(originDisplayId);
        DisplayLayout currentDisplayLayout = mDisplayController.getDisplayLayout(currentDisplayId);

        if (originDisplayLayout == null || currentDisplayLayout == null) {
            ProtoLog.w(WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: Failed to show drag mirror on connected displays because displayLayout "
                            + "is null", TAG);
            return;
        }

        RectF globalDpPipBounds =
                MultiDisplayDragMoveBoundsCalculator.calculateGlobalDpBoundsForDrag(
                originDisplayLayout, originPointerCoordinates, originBounds, currentDisplayLayout,
                currentPointerCoordinates.x, currentPointerCoordinates.y
        );

        final Transaction transaction = mSurfaceControlTransactionFactory.getTransaction();
        // Iterate through each connected display ID to ensure partial PiP bounds are shown on
        // all corresponding displays while dragging
        for (int displayId : mRootTaskDisplayAreaOrganizer.getDisplayIds()) {
            if (displayId == originDisplayId) continue;

            DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId);
            if (displayLayout == null) continue;

            // If PiP does not cross the boundaries of a given display bounds, skip
            boolean shouldShowOnDisplay = RectF.intersects(globalDpPipBounds,
                    displayLayout.globalBoundsDp());
            if (!shouldShowOnDisplay) continue;

            // Create a mirror for the current display if it hasn't been created yet
            SurfaceControl mirror;
            if (!mOnDragMirrorPerDisplayId.containsKey(displayId)) {
                mirror = SurfaceControl.mirrorSurface(mPipTransitionState.getPinnedTaskLeash());
                mOnDragMirrorPerDisplayId.put(displayId, mirror);
            } else {
                mirror = mOnDragMirrorPerDisplayId.get(displayId);
            }

            // Convert the PiP bounds in dp to px based on the current display layout
            final Rect boundsOnCurrentDisplay =
                    MultiDisplayDragMoveBoundsCalculator.convertGlobalDpToLocalPxForRect(
                            globalDpPipBounds, displayLayout);

            mPipScheduler.setPipTransformations(mirror, transaction,
                    boundsOnCurrentDisplay, /* degrees= */ 0);
            mRootTaskDisplayAreaOrganizer.reparentToDisplayArea(displayId, mirror, transaction);
            transaction.show(mirror);
        }
        transaction.apply();
    }

    /**
     * Remove all drag indicator mirrors from each connected display.
     */
    // TODO(b/408981327): Remove mirrors on screen lock
    // TODO(b/408982524): Remove mirrors on opening app while dragging
    public void removeMirrors() {
        final Transaction transaction = mSurfaceControlTransactionFactory.getTransaction();
        for (SurfaceControl mirror : mOnDragMirrorPerDisplayId.values()) {
            transaction.remove(mirror);
        }
        transaction.apply();
        mOnDragMirrorPerDisplayId.clear();
    }

    @VisibleForTesting
    void setSurfaceControlTransactionFactory(
            @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
        mSurfaceControlTransactionFactory = factory;
    }
}
+17 −3
Original line number Diff line number Diff line
@@ -279,6 +279,20 @@ public class PipScheduler implements PipTransitionState.PipTransitionStateChange
        SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();

        setPipTransformations(leash, tx, toBounds, degrees);
        tx.apply();
    }

    /**
     * Sets PiP translational, scaling and rotational transformations on a given transaction.
     *
     * @param leash PiP leash to apply the transformations on
     * @param outTransaction transaction to set the matrix on
     * @param toBounds bounds to position the PiP to
     * @param degrees the angle to rotate the bounds to
     */
    public void setPipTransformations(SurfaceControl leash,
            SurfaceControl.Transaction outTransaction, Rect toBounds, float degrees) {
        Matrix transformTensor = new Matrix();
        final float[] mMatrixTmp = new float[9];
        final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width();
@@ -287,10 +301,10 @@ public class PipScheduler implements PipTransitionState.PipTransitionStateChange
        transformTensor.postTranslate(toBounds.left, toBounds.top);
        transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY());

        mPipSurfaceTransactionHelper.round(tx, leash, mPipBoundsState.getBounds(), toBounds);
        mPipSurfaceTransactionHelper.round(outTransaction, leash, mPipBoundsState.getBounds(),
                toBounds);

        tx.setMatrix(leash, transformTensor, mMatrixTmp);
        tx.apply();
        outTransaction.setMatrix(leash, transformTensor, mMatrixTmp);
    }

    void startOverlayFadeoutAnimation(@NonNull SurfaceControl overlayLeash,
Loading