summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Hongwei Wang <hwwang@google.com> 2024-11-04 17:43:25 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-11-04 17:43:25 +0000
commit1b259fc1f72a30c4586ee31d9b6580911ef794e5 (patch)
tree2348293b58d48c2d51153adba7f3c68f4dd0481f
parent25260b2e99c9baf524ee39a22aff708198b4e472 (diff)
parentf7bad0509e7cb9f4947e194c04de6e000fd75313 (diff)
Merge "[PiP2] Add PipEnterAnimatorTest" into main
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java201
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));
+ }
+}