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

Commit ab0b707c authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

[PiP2] Handle enter PiP and display change in sync

Sometimes enter PiP transition can also collect the display change
which would not be hitting the traditional fixed-rotation case.
This happens when TRANSIT_PIP is collecting and display change is also
happening in Core, in which case a separate IDisplayChangeWindowControllerImpl
channel is used to dispatch onDisplayChange callbacks.

This means these onDisplayChange() callbacks can come in after
startWCT was sent to Core, but before we start animating the transition.
So what we can do is recognize this and update the Shell-side PiP bounds
state as needed, fake animate PiP in the desired destination bounds when
the transition arrives to be played, and then kick off a jumpcut bounds
change transition to restore the WM bounds as well.

This happens 100% of the time when entering PiP by tapping elsewhere in
overview from landscape with the resumed activity supporting auto-enter PiP.

Bug: 381186732
Flag: com.android.wm.shell.enable_pip2
Test: tap elsewhere in Overview with PiP-able activity resumed in
landscape

Change-Id: Idc9e9b6d2783e5ab1887a5b436682054851d46ea
parent 9557e44e
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.wm.shell.desktopmode.DesktopPipTransitionController;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.phone.PhonePipMenuController;
import com.android.wm.shell.pip2.phone.PipController;
import com.android.wm.shell.pip2.phone.PipInteractionHandler;
@@ -133,6 +134,7 @@ public abstract class Pip2Module {
            PipAppOpsListener pipAppOpsListener,
            PhonePipMenuController pipMenuController,
            PipUiEventLogger pipUiEventLogger,
            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
            @ShellMainThread ShellExecutor mainExecutor) {
        if (!PipUtils.isPip2ExperimentEnabled()) {
            return Optional.empty();
@@ -142,7 +144,7 @@ public abstract class Pip2Module {
                    displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
                    pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
                    pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController,
                    pipUiEventLogger, mainExecutor));
                    pipUiEventLogger, pipSurfaceTransactionHelper, mainExecutor));
        }
    }

@@ -288,4 +290,10 @@ public abstract class Pip2Module {
        return new PipInteractionHandler(context, mainHandler,
                InteractionJankMonitor.getInstance());
    }

    @WMSingleton
    @Provides
    static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
        return new PipSurfaceTransactionHelper(context);
    }
}
+67 −7
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
import android.util.Log;
import android.view.SurfaceControl;
import android.window.DesktopExperienceFlags;
@@ -66,6 +65,7 @@ import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -87,6 +87,8 @@ public class PipController implements ConfigurationChangeListener,
    private static final String TAG = PipController.class.getSimpleName();
    private static final String SWIPE_TO_PIP_APP_BOUNDS = "pip_app_bounds";
    private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
    private static final String DISPLAY_CHANGE_PIP_BOUNDS_UPDATE =
            "display_change_pip_bounds_update";

    private final Context mContext;
    private final ShellCommandHandler mShellCommandHandler;
@@ -111,6 +113,10 @@ public class PipController implements ConfigurationChangeListener,
    // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
    @Nullable private PipAnimationListener mPipRecentsAnimationListener;

    private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;

    private boolean mWaitingToPlayDisplayChangeBoundsUpdate;

    @VisibleForTesting
    interface PipAnimationListener {
        /**
@@ -150,6 +156,7 @@ public class PipController implements ConfigurationChangeListener,
            PipAppOpsListener pipAppOpsListener,
            PhonePipMenuController pipMenuController,
            PipUiEventLogger pipUiEventLogger,
            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
            ShellExecutor mainExecutor) {
        mContext = context;
        mShellCommandHandler = shellCommandHandler;
@@ -168,6 +175,7 @@ public class PipController implements ConfigurationChangeListener,
        mPipAppOpsListener = pipAppOpsListener;
        mPipMenuController = pipMenuController;
        mPipUiEventLogger = pipUiEventLogger;
        mPipSurfaceTransactionHelper = pipSurfaceTransactionHelper;
        mMainExecutor = mainExecutor;
        mImpl = new PipImpl();

@@ -196,6 +204,7 @@ public class PipController implements ConfigurationChangeListener,
            PipAppOpsListener pipAppOpsListener,
            PhonePipMenuController pipMenuController,
            PipUiEventLogger pipUiEventLogger,
            PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
            ShellExecutor mainExecutor) {
        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -206,7 +215,7 @@ public class PipController implements ConfigurationChangeListener,
                displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
                pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
                pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController,
                pipUiEventLogger, mainExecutor);
                pipUiEventLogger, pipSurfaceTransactionHelper, mainExecutor);
    }

    public PipImpl getPipImpl() {
@@ -342,8 +351,7 @@ public class PipController implements ConfigurationChangeListener,
            mPipDisplayLayoutState.rotateTo(toRotation);
        }

        if (!mPipTransitionState.isInPip()
                && mPipTransitionState.getState() != PipTransitionState.ENTERING_PIP) {
        if (!shouldUpdatePipStateOnDisplayChange()) {
            // Skip the PiP-relevant updates if we aren't in a valid PiP state.
            if (mPipTransitionState.isInFixedRotation()) {
                ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -372,9 +380,13 @@ public class PipController implements ConfigurationChangeListener,
            mPipBoundsState.setBounds(toBounds);
        }
        if (mPipTransitionState.getPipTaskToken() == null) {
            Log.wtf(TAG, "PipController.onDisplayChange no PiP task token"
                    + " state=" + mPipTransitionState.getState()
                    + " callers=\n" + Debug.getCallers(4, "    "));
            Log.d(TAG, "PipController.onDisplayChange no PiP task token"
                    + " state=" + mPipTransitionState.getState());
            mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
                final Bundle extra = new Bundle();
                extra.putBoolean(DISPLAY_CHANGE_PIP_BOUNDS_UPDATE, true);
                mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
            });
        } else {
            t.setBounds(mPipTransitionState.getPipTaskToken(), mPipBoundsState.getBounds());
        }
@@ -386,6 +398,14 @@ public class PipController implements ConfigurationChangeListener,
        mPipDisplayLayoutState.setDisplayLayout(layout);
    }

    private boolean shouldUpdatePipStateOnDisplayChange() {
        // We should at least update internal PiP state, such as PiP bounds state or movement bounds
        // if we are either in PiP or about to enter PiP.
        return mPipTransitionState.isInPip()
                || mPipTransitionState.getState() == PipTransitionState.ENTERING_PIP
                || mPipTransitionState.getState() == PipTransitionState.SCHEDULED_ENTER_PIP;
    }

    //
    // IPip Binder stub helpers
    //
@@ -510,7 +530,47 @@ public class PipController implements ConfigurationChangeListener,
                    listener.accept(false /* inPip */);
                }
                break;
            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
                mWaitingToPlayDisplayChangeBoundsUpdate =
                        extra.getBoolean(DISPLAY_CHANGE_PIP_BOUNDS_UPDATE);
                if (mWaitingToPlayDisplayChangeBoundsUpdate) {
                    // If we reach this point, it means display change did not send through a WCT to
                    // update the pinned task bounds in Core. Instead, the local Shell-side
                    // PiP-relevant bounds state and movement bounds were updated.
                    // So schedule a jumpcut animation to those bounds now.
                    mPipScheduler.scheduleAnimateResizePip(mPipBoundsState.getBounds());
                }
                break;
            case PipTransitionState.CHANGING_PIP_BOUNDS:
                if (!mWaitingToPlayDisplayChangeBoundsUpdate) {
                    break;
                }
                mWaitingToPlayDisplayChangeBoundsUpdate = false;
                final SurfaceControl.Transaction startTx = extra.getParcelable(
                        PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
                final SurfaceControl.Transaction finishTx = extra.getParcelable(
                        PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
                final Rect destinationBounds = extra.getParcelable(
                        PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
                handleJumpcutBoundsUpdate(startTx, finishTx, destinationBounds);
                break;
        }
    }

    private void handleJumpcutBoundsUpdate(SurfaceControl.Transaction startTx,
            SurfaceControl.Transaction finishTx, Rect destinationBounds) {
        SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();

        startTx.merge(finishTx);
        startTx.setPosition(pipLeash, destinationBounds.left, destinationBounds.top);
        mPipSurfaceTransactionHelper.round(startTx, pipLeash, true /* applyCornerRadius */)
                .shadow(startTx, pipLeash, true /* applyShadowRadius */);
        mPipSurfaceTransactionHelper.round(finishTx, pipLeash, true /* applyCornerRadius */)
                .shadow(finishTx, pipLeash, true /* applyShadowRadius */);
        startTx.apply();

        // Signal that the transition is done - should update transition state by default.
        mPipScheduler.scheduleFinishPipBoundsChange(destinationBounds);
    }

    //
+18 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

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

import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Surface.ROTATION_0;
@@ -236,6 +237,7 @@ public class PipTransition extends PipTransitionController implements
            @NonNull TransitionRequestInfo request) {
        if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
            mEnterTransition = transition;
            mPipTransitionState.setState(PipTransitionState.SCHEDULED_ENTER_PIP);
            final WindowContainerTransaction wct = getEnterPipTransaction(transition,
                    request.getPipChange());

@@ -259,6 +261,7 @@ public class PipTransition extends PipTransitionController implements
            outWct.merge(getEnterPipTransaction(transition, request.getPipChange()),
                    true /* transfer */);
            mEnterTransition = transition;
            mPipTransitionState.setState(PipTransitionState.SCHEDULED_ENTER_PIP);
        }
    }

@@ -645,10 +648,22 @@ public class PipTransition extends PipTransitionController implements
        }
        mFinishCallback = finishCallback;

        Rect destinationBounds = pipChange.getEndAbsBounds();
        SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
        final Rect destinationBounds = pipChange.getEndAbsBounds();
        if (pipChange.getEndRotation() != ROTATION_UNDEFINED
                && pipChange.getStartRotation() != pipChange.getEndRotation()) {
            // If we are playing an enter PiP animation with display change collected together
            // in the same transition, then PipController#onDisplayChange() must have already
            // updated the PiP bounds state to reflect the final desired destination bounds.
            // This might not be in the WM state yet as PiP task token might have been null then.
            // WM state will be updated via a follow-up bounds change transition after.
            destinationBounds.set(mPipBoundsState.getBounds());
        }

        final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
        Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition.");

        // Note that fixed rotation is different from the same transition display change rotation;
        // with fixed rotation, we expect a follow-up async rotation transition after this one.
        final int delta = getFixedRotationDelta(info, pipChange, mPipDisplayLayoutState);
        if (delta != ROTATION_0) {
            updatePipChangesForFixedRotation(info, pipChange,
@@ -675,6 +690,7 @@ public class PipTransition extends PipTransitionController implements
            finishTransaction.setMatrix(pipLeash, transformTensor, matrixTmp);
        } else {
            startTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top);
            finishTransaction.setPosition(pipLeash, destinationBounds.left, destinationBounds.top);
        }

        PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipLeash, startTransaction,
+12 −7
Original line number Diff line number Diff line
@@ -76,27 +76,30 @@ public class PipTransitionState {
    // State for Launcher animating the swipe PiP to home animation.
    public static final int SWIPING_TO_PIP = 1;

    // State for scheduling enter PiP transition; could be after SWIPING_TO_PIP
    public static final int SCHEDULED_ENTER_PIP = 2;

    // State for Shell animating enter PiP or jump-cutting to PiP mode after Launcher animation.
    public static final int ENTERING_PIP = 2;
    public static final int ENTERING_PIP = 3;

    // State for app finishing drawing in PiP mode as a final step in enter PiP flow.
    public static final int ENTERED_PIP = 3;
    public static final int ENTERED_PIP = 4;

    // State to indicate we have scheduled a PiP bounds change transition.
    public static final int SCHEDULED_BOUNDS_CHANGE = 4;
    public static final int SCHEDULED_BOUNDS_CHANGE = 5;

    // State for the start of playing a transition to change PiP bounds. At this point, WM Core
    // is aware of the new PiP bounds, but Shell might still be continuing animating.
    public static final int CHANGING_PIP_BOUNDS = 5;
    public static final int CHANGING_PIP_BOUNDS = 6;

    // State for finishing animating into new PiP bounds after resize is complete.
    public static final int CHANGED_PIP_BOUNDS = 6;
    public static final int CHANGED_PIP_BOUNDS = 7;

    // State for starting exiting PiP.
    public static final int EXITING_PIP = 7;
    public static final int EXITING_PIP = 8;

    // State for finishing exit PiP flow.
    public static final int EXITED_PIP = 8;
    public static final int EXITED_PIP = 9;

    private static final int FIRST_CUSTOM_STATE = 1000;

@@ -105,6 +108,7 @@ public class PipTransitionState {
    @IntDef(prefix = { "TRANSITION_STATE_" }, value =  {
            UNDEFINED,
            SWIPING_TO_PIP,
            SCHEDULED_ENTER_PIP,
            ENTERING_PIP,
            ENTERED_PIP,
            SCHEDULED_BOUNDS_CHANGE,
@@ -421,6 +425,7 @@ public class PipTransitionState {
        switch (state) {
            case UNDEFINED: return "undefined";
            case SWIPING_TO_PIP: return "swiping_to_pip";
            case SCHEDULED_ENTER_PIP: return "scheduled_enter_pip";
            case ENTERING_PIP: return "entering-pip";
            case ENTERED_PIP: return "entered-pip";
            case SCHEDULED_BOUNDS_CHANGE: return "scheduled_bounds_change";