Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +70 −54 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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. * Loading Loading @@ -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. * Loading @@ -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); } Loading @@ -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. */ Loading @@ -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; } } libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +2 −8 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -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); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java 0 → 100644 +201 −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 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)); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +70 −54 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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) { Loading @@ -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. * Loading Loading @@ -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. * Loading @@ -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); } Loading @@ -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. */ Loading @@ -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; } }
libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +2 −8 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -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); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java 0 → 100644 +201 −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 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)); } }