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

Commit 23d25864 authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

Resize animation after pinching PiP

Implement resize animator after pinch-release
in PiP2. Make sure that PipTransitionState is
properly set to CHANGED_PIP_BOUNDS only when
the new buffer has arrived and Shell received a signal for it
as a part of the config-at-end transition.

The latter makes sure that PiP touch interactions are not enabled
until the transition is fully complete, and all the relevant state
is up-to-date and ready for the next interaction.

Bug: 346586058
Test: swipe-up enter PiP2 and pinch-resize PiP window

Change-Id: I74607950f20d17953d08b16e3076f0407d8e1aa0
parent 48459cba
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -310,6 +310,14 @@ public abstract class PipTransitionController implements Transitions.TransitionH
    public void end() {
    }

    /**
     * Finish the current transition if possible.
     *
     * @param tx transaction to be applied with a potentially new draw after finishing.
     */
    public void finishTransition(@Nullable SurfaceControl.Transaction tx) {
    }

    /**
     * End the currently-playing PiP animation.
     *
+154 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

import android.animation.Animator;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;

/**
 * Animator that handles any resize related animation for PIP.
 */
public class PipResizeAnimator extends ValueAnimator
        implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener{
    @NonNull
    private final Context mContext;
    @NonNull
    private final SurfaceControl mLeash;
    @Nullable
    private SurfaceControl.Transaction mStartTx;
    @Nullable
    private SurfaceControl.Transaction mFinishTx;
    @Nullable
    private Runnable mAnimationStartCallback;
    @Nullable
    private Runnable mAnimationEndCallback;
    private RectEvaluator mRectEvaluator;
    private final Rect mBaseBounds = new Rect();
    private final Rect mStartBounds = new Rect();
    private final Rect mEndBounds = new Rect();
    private final Rect mAnimatedRect = new Rect();
    private final float mDelta;

    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
            mSurfaceControlTransactionFactory;

    public PipResizeAnimator(@NonNull Context context,
            @NonNull SurfaceControl leash,
            @Nullable SurfaceControl.Transaction startTransaction,
            @Nullable SurfaceControl.Transaction finishTransaction,
            @NonNull Rect baseBounds,
            @NonNull Rect startBounds,
            @NonNull Rect endBounds,
            int duration,
            float delta) {
        mContext = context;
        mLeash = leash;
        mStartTx = startTransaction;
        mFinishTx = finishTransaction;
        mSurfaceControlTransactionFactory =
                new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();

        mBaseBounds.set(baseBounds);
        mStartBounds.set(startBounds);
        mAnimatedRect.set(startBounds);
        mEndBounds.set(endBounds);
        mDelta = delta;

        mRectEvaluator = new RectEvaluator(mAnimatedRect);

        setObjectValues(startBounds, endBounds);
        addListener(this);
        addUpdateListener(this);
        setEvaluator(mRectEvaluator);
        // TODO: change this
        setDuration(duration);
    }

    public void setAnimationStartCallback(@NonNull Runnable runnable) {
        mAnimationStartCallback = runnable;
    }

    public void setAnimationEndCallback(@NonNull Runnable runnable) {
        mAnimationEndCallback = runnable;
    }

    @Override
    public void onAnimationStart(@NonNull Animator animation) {
        if (mAnimationStartCallback != null) {
            mAnimationStartCallback.run();
        }
        if (mStartTx != null) {
            setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
            mStartTx.apply();
        }
    }

    @Override
    public void onAnimationUpdate(@NonNull ValueAnimator animation) {
        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
        final float fraction = getAnimatedFraction();
        final float degrees = (1.0f - fraction) * mDelta;
        setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
        tx.apply();
    }

    /**
     * Set a proper transform matrix for a leash to move it to given bounds with a certain rotation.
     *
     * @param baseBounds crop/buffer size relative to which we are scaling the leash.
     * @param targetBounds bounds to which we are scaling the leash.
     * @param degrees degrees of rotation - counter-clockwise is positive by convention.
     */
    public static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
            Rect baseBounds, Rect targetBounds, float degrees) {
        Matrix transformTensor = new Matrix();
        final float[] mMatrixTmp = new float[9];
        final float scale = (float) targetBounds.width() / baseBounds.width();

        transformTensor.setScale(scale, scale);
        transformTensor.postTranslate(targetBounds.left, targetBounds.top);
        transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY());

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

    @Override
    public void onAnimationEnd(@NonNull Animator animation) {
        if (mFinishTx != null) {
            setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
        }
        if (mAnimationEndCallback != null) {
            mAnimationEndCallback.run();
        }
    }

    @Override
    public void onAnimationCancel(@NonNull Animator animation) {}

    @Override
    public void onAnimationRepeat(@NonNull Animator animation) {}
}
+2 −2
Original line number Diff line number Diff line
@@ -731,8 +731,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
                settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
                cleanUpHighPerfSessionMaybe();

                // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
                mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
                // Signal that the transition is done - should update transition state by default.
                mPipScheduler.scheduleFinishResizePip(false /* configAtEnd */);
                break;
            case PipTransitionState.EXITING_PIP:
                // We need to force finish any local animators if about to leave PiP, to avoid
+31 −15
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import android.view.ViewConfiguration;

import androidx.annotation.VisibleForTesting;

import com.android.internal.util.Preconditions;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -45,6 +46,7 @@ import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip2.animation.PipResizeAnimator;

import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -82,6 +84,7 @@ public class PipResizeGestureHandler implements
    private final Rect mLastResizeBounds = new Rect();
    private final Rect mUserResizeBounds = new Rect();
    private final Rect mDownBounds = new Rect();
    private final Rect mStartBoundsAfterRelease = new Rect();
    private final Runnable mUpdateMovementBoundsRunnable;
    private final Consumer<Rect> mUpdateResizeBoundsCallback;

@@ -418,7 +421,9 @@ public class PipResizeGestureHandler implements
        if (!mOngoingPinchToResize) {
            return;
        }
        final Rect startBounds = new Rect(mLastResizeBounds);

        // Cache initial bounds after release for animation before mLastResizeBounds are modified.
        mStartBoundsAfterRelease.set(mLastResizeBounds);

        // If user resize is pretty close to max size, just auto resize to max.
        if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
@@ -527,28 +532,39 @@ public class PipResizeGestureHandler implements
                    int offsetY = inTopHalf ? 1 : -1;
                    mLastResizeBounds.offset(0 /* dx */, offsetY);
                }

                mWaitingForBoundsChangeTransition = true;
                mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds);

                // Schedule PiP resize transition, but delay any config updates until very end.
                mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds, true /* configAtEnd */);
                break;
            case PipTransitionState.CHANGING_PIP_BOUNDS:
                if (!mWaitingForBoundsChangeTransition) break;

                // If bounds change transition was scheduled from this class, handle leash updates.
                // If resize transition was scheduled from this component, handle leash updates.
                mWaitingForBoundsChangeTransition = false;

                SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash;
                Preconditions.checkState(pipLeash != null,
                        "No leash cached by mPipTransitionState=" + mPipTransitionState);

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

                SurfaceControl.Transaction finishTx = extra.getParcelable(
                        PipTransition.PIP_FINISH_TX, SurfaceControl.Transaction.class);
                startTx.setWindowCrop(pipLeash, mPipBoundsState.getBounds().width(),
                        mPipBoundsState.getBounds().height());

                PipResizeAnimator animator = new PipResizeAnimator(mContext, pipLeash,
                        startTx, finishTx, mPipBoundsState.getBounds(), mStartBoundsAfterRelease,
                        mLastResizeBounds, PINCH_RESIZE_SNAP_DURATION, mAngle);
                animator.setAnimationEndCallback(() -> {
                    // All motion operations have actually finished, so make bounds cache updates.
                mUpdateResizeBoundsCallback.accept(destinationBounds);
                    mUpdateResizeBoundsCallback.accept(mLastResizeBounds);
                    cleanUpHighPerfSessionMaybe();

                // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
                mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
                    // Signal that we are done with resize transition
                    mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
                });
                animator.start();
                break;
        }
    }
+31 −0
Original line number Diff line number Diff line
@@ -153,14 +153,45 @@ public class PipScheduler {
     * Animates resizing of the pinned stack given the duration.
     */
    public void scheduleAnimateResizePip(Rect toBounds) {
        scheduleAnimateResizePip(toBounds, false /* configAtEnd */);
    }

    /**
     * Animates resizing of the pinned stack given the duration.
     *
     * @param configAtEnd true if we are delaying config updates until the transition ends.
     */
    public void scheduleAnimateResizePip(Rect toBounds, boolean configAtEnd) {
        if (mPipTransitionState.mPipTaskToken == null || !mPipTransitionState.isInPip()) {
            return;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
        if (configAtEnd) {
            wct.deferConfigToTransitionEnd(mPipTransitionState.mPipTaskToken);
        }
        mPipTransitionController.startResizeTransition(wct);
    }

    /**
     * Signals to Core to finish the PiP resize transition.
     * Note that we do not allow any actual WM Core changes at this point.
     *
     * @param configAtEnd true if we are waiting for config updates at the end of the transition.
     */
    public void scheduleFinishResizePip(boolean configAtEnd) {
        SurfaceControl.Transaction tx = null;
        if (configAtEnd) {
            tx = new SurfaceControl.Transaction();
            tx.addTransactionCommittedListener(mMainExecutor, () -> {
                mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
            });
        } else {
            mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
        }
        mPipTransitionController.finishTransition(tx);
    }

    /**
     * Directly perform a scaled matrix transformation on the leash. This will not perform any
     * {@link WindowContainerTransaction}.
Loading