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

Commit 91584da3 authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

[2/2][PiP2] Block duplicate exit PiP calls

1. Move the setting of EXITING_PIP state up to where
it's scheduled.

2. Block exit/remove PiP calls if not in PiP
(e.g. if exit is already scheduled)

3. Block the scheduling of resizes if not in PiP
through shouldTransitionToState() checks.

4. Separate the scheduling of a remove transition from
an exit transition - also allow the choice of animation w/o fadeout.

Bug: 381017000
Flag: com.android.wm.shell.enable_pip2
Test: atest PipSchedulerTest
Test: atest PinnedStackTests#testPreventSetAspectRatioWhileExpanding
Test: atest PipTransitionStateTest
Test: manually repro steps in the bug
Change-Id: Ief665f45e85be567c3896c336833a8f48a0894a8
parent ee6f46ac
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -128,6 +128,20 @@ public abstract class PipTransitionController implements Transitions.TransitionH
        // Default implementation does nothing.
    }

    /**
     * Called when the Shell wants to start an exit-via-expand from Pip transition/animation.
     */
    public void startExpandTransition(WindowContainerTransaction out) {
        // Default implementation does nothing.
    }

    /**
     * Called when the Shell wants to start a remove Pip transition/animation.
     */
    public void startRemoveTransition(boolean withFadeout) {
        // Default implementation does nothing.
    }

    /**
     * Called when the Shell wants to start resizing Pip transition/animation.
     *
+11 −3
Original line number Diff line number Diff line
@@ -344,13 +344,22 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
     */
    @Override
    public void dismissPip() {
        dismissPip(true /* withFadeout */);
    }

    /**
     * Dismisses the pinned stack.
     *
     * @param withFadeout should animate with fadeout for the removal
     */
    public void dismissPip(boolean withFadeout) {
        if (DEBUG) {
            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, "    "));
        }
        cancelPhysicsAnimation();
        mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
        mPipScheduler.scheduleRemovePip();
        mPipScheduler.scheduleRemovePip(withFadeout);
    }

    /** Sets the movement bounds to use to constrain PIP position animations. */
@@ -473,7 +482,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
                        mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2,
                        0,
                        mSpringConfig)
                .withEndActions(this::dismissPip);
                .withEndActions(() -> dismissPip(false /* withFadeout */));

        startBoundsAnimator(
                getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */);
@@ -772,7 +781,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
            case PipTransitionState.EXITING_PIP:
                // We need to force finish any local animators if about to leave PiP, to avoid
                // breaking the state (e.g. leashes are cleaned up upon exit).
                if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
                cancelPhysicsAnimation();
                settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
                break;
+7 −26
Original line number Diff line number Diff line
@@ -19,9 +19,6 @@ package com.android.wm.shell.pip2.phone;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -105,19 +102,6 @@ public class PipScheduler {
        return wct;
    }

    @Nullable
    private WindowContainerTransaction getRemovePipTransaction() {
        WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
        if (pipTaskToken == null) {
            return null;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setBounds(pipTaskToken, null);
        wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
        wct.reorder(pipTaskToken, false);
        return wct;
    }

    /**
     * Schedules exit PiP via expand transition.
     */
@@ -126,21 +110,16 @@ public class PipScheduler {
            if (!mPipTransitionState.isInPip()) return;
            WindowContainerTransaction wct = getExitPipViaExpandTransaction();
            if (wct != null) {
                mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct,
                        null /* destinationBounds */);
                mPipTransitionController.startExpandTransition(wct);
            }
        });
    }

    /** Schedules remove PiP transition. */
    public void scheduleRemovePip() {
    public void scheduleRemovePip(boolean withFadeout) {
        mMainExecutor.execute(() -> {
            if (!mPipTransitionState.isInPip()) return;
            WindowContainerTransaction wct = getRemovePipTransaction();
            if (wct != null) {
                mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
                        null /* destinationBounds */);
            }
            mPipTransitionController.startRemoveTransition(withFadeout);
        });
    }

@@ -209,9 +188,11 @@ public class PipScheduler {
     * @param degrees the angle to rotate the bounds to.
     */
    public void scheduleUserResizePip(Rect toBounds, float degrees) {
        if (toBounds.isEmpty()) {
        if (toBounds.isEmpty() || !mPipTransitionState.isInPip()) {
            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
                    "%s: Attempted to user resize PIP in invalid state, aborting;"
                            + "toBounds=%s, mPipTransitionState=%s",
                    TAG, toBounds, mPipTransitionState);
            return;
        }
        SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
+41 −14
Original line number Diff line number Diff line
@@ -18,6 +18,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;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -118,6 +119,7 @@ public class PipTransition extends PipTransitionController implements
    @Nullable
    private IBinder mResizeTransition;
    private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
    private boolean mPendingRemoveWithFadeout;


    //
@@ -170,15 +172,19 @@ public class PipTransition extends PipTransitionController implements
    //

    @Override
    public void startExitTransition(int type, WindowContainerTransaction out,
            @Nullable Rect destinationBounds) {
        if (out == null) {
            return;
        }
        IBinder transition = mTransitions.startTransition(type, out, this);
        if (type == TRANSIT_EXIT_PIP) {
            mExitViaExpandTransition = transition;
    public void startExpandTransition(WindowContainerTransaction out) {
        if (out == null) return;
        mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
        mExitViaExpandTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
    }

    @Override
    public void startRemoveTransition(boolean withFadeout) {
        final WindowContainerTransaction wct = getRemovePipTransaction();
        if (wct == null) return;
        mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
        mPendingRemoveWithFadeout = withFadeout;
        mTransitions.startTransition(TRANSIT_REMOVE_PIP, wct, this);
    }

    @Override
@@ -270,7 +276,6 @@ public class PipTransition extends PipTransitionController implements
                    finishCallback);
        } else if (transition == mExitViaExpandTransition) {
            mExitViaExpandTransition = null;
            mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
            return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
        } else if (transition == mResizeTransition) {
            mResizeTransition = null;
@@ -676,11 +681,19 @@ public class PipTransition extends PipTransitionController implements
        TransitionInfo.Change pipChange = getChangeByToken(info,
                mPipTransitionState.getPipTaskToken());
        mFinishCallback = finishCallback;

        finishTransaction.setAlpha(pipChange.getLeash(), 0f);
        if (mPendingRemoveWithFadeout) {
            PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
                    startTransaction, PipAlphaAnimator.FADE_OUT);
        finishTransaction.setAlpha(pipChange.getLeash(), 0f);
            animator.setAnimationEndCallback(this::finishTransition);
            animator.start();
        } else {
            // Jumpcut to a faded-out PiP if no fadeout animation was requested.
            startTransaction.setAlpha(pipChange.getLeash(), 0f);
            startTransaction.apply();
            finishTransition();
        }
        return true;
    }

@@ -810,6 +823,19 @@ public class PipTransition extends PipTransitionController implements
        return wct;
    }

    @Nullable
    private WindowContainerTransaction getRemovePipTransaction() {
        WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
        if (pipTaskToken == null) {
            return null;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setBounds(pipTaskToken, null);
        wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
        wct.reorder(pipTaskToken, false);
        return wct;
    }

    private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
        final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipChange() != null
                ? requestInfo.getPipChange().getTaskInfo() : null;
@@ -953,6 +979,7 @@ public class PipTransition extends PipTransitionController implements
            case PipTransitionState.EXITED_PIP:
                mPipTransitionState.setPinnedTaskLeash(null);
                mPipTransitionState.setPipTaskInfo(null);
                mPendingRemoveWithFadeout = false;
                break;
        }
    }
+20 −0
Original line number Diff line number Diff line
@@ -27,7 +27,9 @@ import android.window.WindowContainerToken;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellMainThread;

import java.io.PrintWriter;
@@ -201,6 +203,13 @@ public class PipTransitionState {
            Preconditions.checkArgument(extra != null && !extra.isEmpty(),
                    "No extra bundle for " + stateToString(state) + " state.");
        }
        if (!shouldTransitionToState(state)) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: Attempted to transition to an invalid state=%s, while in %s",
                    TAG, stateToString(state), this);
            return;
        }

        if (mState != state) {
            final int prevState = mState;
            mState = state;
@@ -374,6 +383,17 @@ public class PipTransitionState {
        return ++mPrevCustomState;
    }

    private boolean shouldTransitionToState(@TransitionState int newState) {
        switch (newState) {
            case SCHEDULED_BOUNDS_CHANGE:
                // Allow scheduling bounds change only while in PiP, except for if another bounds
                // change was scheduled but hasn't started playing yet.
                return isInPip();
            default:
                return true;
        }
    }

    private static String stateToString(int state) {
        switch (state) {
            case UNDEFINED: return "undefined";
Loading