Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +8 −0 Original line number Diff line number Diff line Loading @@ -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. * Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java 0 → 100644 +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) {} } libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +31 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; } } Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +31 −0 Original line number Diff line number Diff line Loading @@ -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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +8 −0 Original line number Diff line number Diff line Loading @@ -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. * Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java 0 → 100644 +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) {} }
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +31 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +31 −0 Original line number Diff line number Diff line Loading @@ -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