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

Commit f983dd27 authored by Ikram Gabiyev's avatar Ikram Gabiyev Committed by Android (Google) Code Review
Browse files

Merge "Resize animation after pinching PiP" into main

parents b01310cb 23d25864
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