diff options
| author | 2024-11-04 17:43:25 +0000 | |
|---|---|---|
| committer | 2024-11-04 17:43:25 +0000 | |
| commit | 1b259fc1f72a30c4586ee31d9b6580911ef794e5 (patch) | |
| tree | 2348293b58d48c2d51153adba7f3c68f4dd0481f | |
| parent | 25260b2e99c9baf524ee39a22aff708198b4e472 (diff) | |
| parent | f7bad0509e7cb9f4947e194c04de6e000fd75313 (diff) | |
Merge "[PiP2] Add PipEnterAnimatorTest" into main
3 files changed, 273 insertions, 62 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java index e5137582822d..eb33ff4c1c8e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java @@ -20,6 +20,7 @@ import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.content.Context; @@ -34,6 +35,7 @@ import android.window.TransitionInfo; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.common.pip.PipUtils; @@ -45,8 +47,7 @@ import com.android.wm.shell.shared.pip.PipContentOverlay; /** * Animator that handles bounds animations for entering PIP. */ -public class PipEnterAnimator extends ValueAnimator - implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { +public class PipEnterAnimator extends ValueAnimator { @NonNull private final SurfaceControl mLeash; private final SurfaceControl.Transaction mStartTransaction; private final SurfaceControl.Transaction mFinishTransaction; @@ -56,49 +57,82 @@ public class PipEnterAnimator extends ValueAnimator private final RectEvaluator mRectEvaluator; private final Rect mEndBounds = new Rect(); - @Nullable private final Rect mSourceRectHint; private final @Surface.Rotation int mRotation; @Nullable private Runnable mAnimationStartCallback; @Nullable private Runnable mAnimationEndCallback; - private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; Matrix mTransformTensor = new Matrix(); final float[] mMatrixTmp = new float[9]; @Nullable private PipContentOverlay mContentOverlay; + private PipAppIconOverlaySupplier mPipAppIconOverlaySupplier; // Internal state representing initial transform - cached to avoid recalculation. private final PointF mInitScale = new PointF(); private final PointF mInitPos = new PointF(); private final Rect mInitCrop = new Rect(); - private final PointF mInitActivityScale = new PointF(); - private final PointF mInitActivityPos = new PointF(); + + private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + super.onAnimationStart(animation); + if (mAnimationStartCallback != null) { + mAnimationStartCallback.run(); + } + if (mStartTransaction != null) { + onEnterAnimationUpdate(0f /* fraction */, mStartTransaction); + mStartTransaction.apply(); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (mFinishTransaction != null) { + onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction); + } + if (mAnimationEndCallback != null) { + mAnimationEndCallback.run(); + } + } + }; + + private final AnimatorUpdateListener mAnimatorUpdateListener = new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + final SurfaceControl.Transaction tx = + mSurfaceControlTransactionFactory.getTransaction(); + final float fraction = getAnimatedFraction(); + onEnterAnimationUpdate(fraction, tx); + tx.apply(); + } + }; public PipEnterAnimator(Context context, @NonNull SurfaceControl leash, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, @NonNull Rect endBounds, - @Nullable Rect sourceRectHint, @Surface.Rotation int rotation) { mLeash = leash; mStartTransaction = startTransaction; mFinishTransaction = finishTransaction; mRectEvaluator = new RectEvaluator(mAnimatedRect); mEndBounds.set(endBounds); - mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null; mRotation = rotation; mSurfaceControlTransactionFactory = new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); + mPipAppIconOverlaySupplier = this::getAppIconOverlay; final int enterAnimationDuration = context.getResources() .getInteger(R.integer.config_pipEnterAnimationDuration); setDuration(enterAnimationDuration); setFloatValues(0f, 1f); setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - addListener(this); - addUpdateListener(this); + addListener(mAnimatorListener); + addUpdateListener(mAnimatorUpdateListener); } public void setAnimationStartCallback(@NonNull Runnable runnable) { @@ -109,35 +143,6 @@ public class PipEnterAnimator extends ValueAnimator mAnimationEndCallback = runnable; } - @Override - public void onAnimationStart(@NonNull Animator animation) { - if (mAnimationStartCallback != null) { - mAnimationStartCallback.run(); - } - if (mStartTransaction != null) { - onEnterAnimationUpdate(0f /* fraction */, mStartTransaction); - mStartTransaction.apply(); - } - } - - @Override - public void onAnimationEnd(@NonNull Animator animation) { - if (mFinishTransaction != null) { - onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction); - } - if (mAnimationEndCallback != null) { - mAnimationEndCallback.run(); - } - } - - @Override - public void onAnimationUpdate(@NonNull ValueAnimator animation) { - final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); - final float fraction = getAnimatedFraction(); - onEnterAnimationUpdate(fraction, tx); - tx.apply(); - } - /** * Updates the transaction to reflect the state of PiP leash at a certain fraction during enter. * @@ -177,14 +182,6 @@ public class PipEnterAnimator extends ValueAnimator } } - // no-ops - - @Override - public void onAnimationCancel(@NonNull Animator animation) {} - - @Override - public void onAnimationRepeat(@NonNull Animator animation) {} - /** * Caches the initial transform relevant values for the bounds enter animation. * @@ -201,18 +198,13 @@ public class PipEnterAnimator extends ValueAnimator */ public void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds, ActivityInfo activityInfo, int appIconSizePx) { - reattachAppIconOverlay( - new PipAppIconOverlay(context, appBounds, destinationBounds, - new IconProvider(context).getIcon(activityInfo), appIconSizePx)); - } - - private void reattachAppIconOverlay(PipAppIconOverlay overlay) { final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); if (mContentOverlay != null) { mContentOverlay.detach(tx); } - mContentOverlay = overlay; + mContentOverlay = mPipAppIconOverlaySupplier.get(context, appBounds, destinationBounds, + activityInfo, appIconSizePx); mContentOverlay.attach(tx, mLeash); } @@ -229,6 +221,13 @@ public class PipEnterAnimator extends ValueAnimator mContentOverlay = null; } + private PipAppIconOverlay getAppIconOverlay( + Context context, Rect appBounds, Rect destinationBounds, + ActivityInfo activityInfo, int iconSize) { + return new PipAppIconOverlay(context, appBounds, destinationBounds, + new IconProvider(context).getIcon(activityInfo), iconSize); + } + /** * @return the app icon overlay leash; null if no overlay is attached. */ @@ -239,4 +238,21 @@ public class PipEnterAnimator extends ValueAnimator } return mContentOverlay.getLeash(); } + + @VisibleForTesting + void setSurfaceControlTransactionFactory( + @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { + mSurfaceControlTransactionFactory = factory; + } + + @VisibleForTesting + interface PipAppIconOverlaySupplier { + PipAppIconOverlay get(Context context, Rect appBounds, Rect destinationBounds, + ActivityInfo activityInfo, int iconSize); + } + + @VisibleForTesting + void setPipAppIconOverlaySupplier(@NonNull PipAppIconOverlaySupplier supplier) { + mPipAppIconOverlaySupplier = supplier; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 64d8887ae5cd..6bf92f69cfb6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -352,17 +352,11 @@ public class PipTransition extends PipTransitionController implements handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation); } - Rect sourceRectHint = null; - if (pipChange.getTaskInfo() != null - && pipChange.getTaskInfo().pictureInPictureParams != null) { - sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint(); - } - prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange, pipActivityChange); startTransaction.merge(finishTransaction); PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, - startTransaction, finishTransaction, destinationBounds, sourceRectHint, delta); + startTransaction, finishTransaction, destinationBounds, delta); animator.setEnterStartState(pipChange); animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction); startTransaction.apply(); @@ -433,7 +427,7 @@ public class PipTransition extends PipTransitionController implements } PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, - startTransaction, finishTransaction, endBounds, adjustedSourceRectHint, delta); + startTransaction, finishTransaction, endBounds, delta); if (sourceRectHint == null) { // update the src-rect-hint in params in place, to set up initial animator transform. params.getSourceRectHint().set(adjustedSourceRectHint); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java new file mode 100644 index 000000000000..a4008c1e6995 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java @@ -0,0 +1,201 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Resources; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.Surface; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; +import com.android.wm.shell.pip2.phone.PipAppIconOverlay; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit test again {@link PipEnterAnimator}. + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class PipEnterAnimatorTest { + + @Mock private Context mMockContext; + + @Mock private Resources mMockResources; + + @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; + + @Mock private SurfaceControl.Transaction mMockAnimateTransaction; + + @Mock private SurfaceControl.Transaction mMockStartTransaction; + + @Mock private SurfaceControl.Transaction mMockFinishTransaction; + + @Mock private Runnable mMockStartCallback; + + @Mock private Runnable mMockEndCallback; + + @Mock private PipAppIconOverlay mMockPipAppIconOverlay; + + @Mock private SurfaceControl mMockAppIconOverlayLeash; + + @Mock private ActivityInfo mMockActivityInfo; + + @Surface.Rotation private int mRotation; + private SurfaceControl mTestLeash; + private Rect mEndBounds; + private PipEnterAnimator mPipEnterAnimator; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mMockContext.getResources()).thenReturn(mMockResources); + when(mMockResources.getInteger(anyInt())).thenReturn(0); + when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction); + when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockAnimateTransaction); + when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockStartTransaction); + when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(mMockFinishTransaction); + when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash); + + mTestLeash = new SurfaceControl.Builder() + .setContainerLayer() + .setName("PipExpandAnimatorTest") + .setCallsite("PipExpandAnimatorTest") + .build(); + } + + @Test + public void setAnimationStartCallback_enter_callbackStartCallback() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + + mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback); + mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mPipEnterAnimator.start(); + mPipEnterAnimator.pause(); + }); + + verify(mMockStartCallback).run(); + verifyZeroInteractions(mMockEndCallback); + } + + @Test + public void setAnimationEndCallback_enter_callbackStartAndEndCallback() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + + mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback); + mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback); + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mPipEnterAnimator.start(); + mPipEnterAnimator.end(); + }); + + verify(mMockStartCallback).run(); + verify(mMockEndCallback).run(); + } + + @Test + public void setAppIconContentOverlay_thenGetContentOverlayLeash_returnOverlayLeash() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + mPipEnterAnimator.setPipAppIconOverlaySupplier( + (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay); + + mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds, + mMockActivityInfo, 64 /* iconSize */); + + assertEquals(mPipEnterAnimator.getContentOverlayLeash(), mMockAppIconOverlayLeash); + } + + @Test + public void setAppIconContentOverlay_thenClearAppIconOverlay_returnNullLeash() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + mPipEnterAnimator.setPipAppIconOverlaySupplier( + (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay); + + mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds, + mMockActivityInfo, 64 /* iconSize */); + mPipEnterAnimator.clearAppIconOverlay(); + + assertNull(mPipEnterAnimator.getContentOverlayLeash()); + } + + @Test + public void onEnterAnimationUpdate_withContentOverlay_animateOverlay() { + mRotation = Surface.ROTATION_0; + mEndBounds = new Rect(100, 100, 500, 500); + mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash, + mMockStartTransaction, mMockFinishTransaction, + mEndBounds, mRotation); + mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory); + mPipEnterAnimator.setPipAppIconOverlaySupplier( + (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay); + + float fraction = 0.5f; + mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds, + mMockActivityInfo, 64 /* iconSize */); + mPipEnterAnimator.onEnterAnimationUpdate(fraction, mMockAnimateTransaction); + + verify(mMockPipAppIconOverlay).onAnimationUpdate( + eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds)); + } +} |