diff options
| author | 2024-11-04 14:22:17 -0800 | |
|---|---|---|
| committer | 2024-11-05 14:59:09 -0800 | |
| commit | bf4a1d7b2464db8d83fe2fa0d51147196271d28b (patch) | |
| tree | 7e5ff70b30ecca689bcf5dd1be9cac759502a2f7 | |
| parent | bf7adc1c5be602d2ff1b4dfbc3c7ed42703cba2e (diff) | |
[PiP2] Add PipTaskListenerTest
Added the PipTaskListenerTest for PipTaskListener class.
Classes are optimized for testing in this change
- Added getter/setter for PipTransitionState#mPinnedTaskLeash
- Allow using the mocked PipResizeAnimator in PipTaskListener
Flag: com.android.wm.shell.enable_pip2
Bug: 376133026
Test: atest WMShellUnitTests:PipTaskListenerTest
Change-Id: Id96cf41fd5b7176d49b1c2f4a54a90af24583bb1
9 files changed, 382 insertions, 21 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java index 58d2a8577d8c..44900ce1db8a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PhonePipMenuController.java @@ -573,7 +573,7 @@ public class PhonePipMenuController implements PipMenuController, @PipTransitionState.TransitionState int newState, Bundle extra) { switch (newState) { case PipTransitionState.ENTERED_PIP: - attach(mPipTransitionState.mPinnedTaskLeash); + attach(mPipTransitionState.getPinnedTaskLeash()); break; case PipTransitionState.EXITED_PIP: detach(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 17392bc521d8..3738353dd0a3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -785,7 +785,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void handleFlingTransition(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds) { - startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, + startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(), destinationBounds.left, destinationBounds.top); startTx.apply(); @@ -799,7 +799,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void startResizeAnimation(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds, int duration) { - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkState(pipLeash != null, "No leash cached by mPipTransitionState=" + mPipTransitionState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index 751175f0f3e9..d98be55f28e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -531,7 +531,7 @@ public class PipResizeGestureHandler implements // If resize transition was scheduled from this component, handle leash updates. mWaitingForBoundsChangeTransition = false; - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkState(pipLeash != null, "No leash cached by mPipTransitionState=" + mPipTransitionState); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java index 8b25b11e3a47..607de0eccd77 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java @@ -118,7 +118,7 @@ public class PipScheduler { public void removePipAfterAnimation() { SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); PipAlphaAnimator animator = new PipAlphaAnimator(mContext, - mPipTransitionState.mPinnedTaskLeash, tx, PipAlphaAnimator.FADE_OUT); + mPipTransitionState.getPinnedTaskLeash(), tx, PipAlphaAnimator.FADE_OUT); animator.setAnimationEndCallback(this::scheduleRemovePipImmediately); animator.start(); } @@ -203,7 +203,7 @@ public class PipScheduler { "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG); return; } - SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash(); final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); Matrix transformTensor = new Matrix(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java index 1b5eebaa8aa1..2f9371536a16 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java @@ -29,6 +29,7 @@ import android.view.SurfaceControl; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.Preconditions; import com.android.wm.shell.ShellTaskOrganizer; @@ -51,7 +52,8 @@ import java.util.List; public class PipTaskListener implements ShellTaskOrganizer.TaskListener, PipTransitionState.PipTransitionStateChangedListener { private static final int ASPECT_RATIO_CHANGE_DURATION = 250; - private static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change"; + @VisibleForTesting + static final String ANIMATING_ASPECT_RATIO_CHANGE = "animating_aspect_ratio_change"; private final Context mContext; private final PipTransitionState mPipTransitionState; @@ -65,6 +67,8 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, private boolean mWaitingForAspectRatioChange = false; private final List<PipParamsChangedCallback> mPipParamsChangedListeners = new ArrayList<>(); + private PipResizeAnimatorSupplier mPipResizeAnimatorSupplier; + public PipTaskListener(Context context, ShellTaskOrganizer shellTaskOrganizer, PipTransitionState pipTransitionState, @@ -86,6 +90,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP); }); } + mPipResizeAnimatorSupplier = PipResizeAnimator::new; } void setPictureInPictureParams(@Nullable PictureInPictureParams params) { @@ -172,19 +177,18 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, final int duration = extra.getInt(ANIMATING_BOUNDS_CHANGE_DURATION, PipTransition.BOUNDS_CHANGE_JUMPCUT_DURATION); - Preconditions.checkNotNull(mPipTransitionState.mPinnedTaskLeash, + Preconditions.checkNotNull(mPipTransitionState.getPinnedTaskLeash(), "Leash is null for bounds transition."); if (mWaitingForAspectRatioChange) { mWaitingForAspectRatioChange = false; - PipResizeAnimator animator = new PipResizeAnimator(mContext, - mPipTransitionState.mPinnedTaskLeash, startTx, finishTx, + PipResizeAnimator animator = mPipResizeAnimatorSupplier.get(mContext, + mPipTransitionState.getPinnedTaskLeash(), startTx, finishTx, destinationBounds, mPipBoundsState.getBounds(), destinationBounds, duration, 0f /* delta */); - animator.setAnimationEndCallback(() -> { - mPipScheduler.scheduleFinishResizePip(destinationBounds); - }); + animator.setAnimationEndCallback( + () -> mPipScheduler.scheduleFinishResizePip(destinationBounds)); animator.start(); } break; @@ -198,4 +202,22 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener, default void onActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) { } } + + @VisibleForTesting + interface PipResizeAnimatorSupplier { + PipResizeAnimator get(@NonNull Context context, + @NonNull SurfaceControl leash, + @Nullable SurfaceControl.Transaction startTx, + @Nullable SurfaceControl.Transaction finishTx, + @NonNull Rect baseBounds, + @NonNull Rect startBounds, + @NonNull Rect endBounds, + int duration, + float delta); + } + + @VisibleForTesting + void setPipResizeAnimatorSupplier(@NonNull PipResizeAnimatorSupplier supplier) { + mPipResizeAnimatorSupplier = supplier; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 9af1aba51c57..65972fb7df48 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -875,7 +875,7 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mPipBoundsState.getMovementBounds().bottom; mMotionHelper.setSpringingToTouch(false); - mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash); + mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.getPinnedTaskLeash()); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it 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 3930b423d8a1..ea783e9cadb6 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 @@ -397,7 +397,7 @@ public class PipTransition extends PipTransitionController implements final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio); - final SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); // For opening type transitions, if there is a change of mode TO_FRONT/OPEN, // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f @@ -521,7 +521,7 @@ public class PipTransition extends PipTransitionController implements } Rect destinationBounds = pipChange.getEndAbsBounds(); - SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); Preconditions.checkNotNull(pipLeash, "Leash is null for alpha transition."); // Start transition with 0 alpha at the entry bounds. @@ -797,17 +797,17 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState.mPipTaskToken = extra.getParcelable( PIP_TASK_TOKEN, WindowContainerToken.class); - mPipTransitionState.mPinnedTaskLeash = extra.getParcelable( - PIP_TASK_LEASH, SurfaceControl.class); + mPipTransitionState.setPinnedTaskLeash(extra.getParcelable( + PIP_TASK_LEASH, SurfaceControl.class)); boolean hasValidTokenAndLeash = mPipTransitionState.mPipTaskToken != null - && mPipTransitionState.mPinnedTaskLeash != null; + && mPipTransitionState.getPinnedTaskLeash() != null; Preconditions.checkState(hasValidTokenAndLeash, "Unexpected bundle for " + mPipTransitionState); break; case PipTransitionState.EXITED_PIP: mPipTransitionState.mPipTaskToken = null; - mPipTransitionState.mPinnedTaskLeash = null; + mPipTransitionState.setPinnedTaskLeash(null); break; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index ccdd66b5d1a8..03e06f906015 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -142,7 +142,7 @@ public class PipTransitionState { // pinned PiP task's leash @Nullable - SurfaceControl mPinnedTaskLeash; + private SurfaceControl mPinnedTaskLeash; // Overlay leash potentially used during swipe PiP to home transition; // if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid. @@ -304,6 +304,14 @@ public class PipTransitionState { mSwipePipToHomeAppBounds.setEmpty(); } + @Nullable SurfaceControl getPinnedTaskLeash() { + return mPinnedTaskLeash; + } + + void setPinnedTaskLeash(@Nullable SurfaceControl leash) { + mPinnedTaskLeash = leash; + } + /** * @return true if either in swipe or button-nav fixed rotation. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java new file mode 100644 index 000000000000..89cb729d17b5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java @@ -0,0 +1,331 @@ +/* + * 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.phone; + +import static com.android.wm.shell.pip2.phone.PipTaskListener.ANIMATING_ASPECT_RATIO_CHANGE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; +import static org.mockito.kotlin.MatchersKt.eq; +import static org.mockito.kotlin.VerificationKt.clearInvocations; +import static org.mockito.kotlin.VerificationKt.times; +import static org.mockito.kotlin.VerificationKt.verify; +import static org.mockito.kotlin.VerificationKt.verifyZeroInteractions; + +import android.app.ActivityManager; +import android.app.PendingIntent; +import android.app.PictureInPictureParams; +import android.app.RemoteAction; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.Rational; +import android.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.pip2.animation.PipResizeAnimator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Unit test against {@link PipTaskListener}. + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class PipTaskListenerTest { + + @Mock private Context mMockContext; + @Mock private ShellTaskOrganizer mMockShellTaskOrganizer; + @Mock private PipTransitionState mMockPipTransitionState; + @Mock private SurfaceControl mMockLeash; + @Mock private PipScheduler mMockPipScheduler; + @Mock private PipBoundsState mMockPipBoundsState; + @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm; + @Mock private ShellExecutor mMockShellExecutor; + + @Mock private Icon mMockIcon; + @Mock private PendingIntent mMockPendingIntent; + + @Mock private PipTaskListener.PipParamsChangedCallback mMockPipParamsChangedCallback; + + @Mock private PipResizeAnimator mMockPipResizeAnimator; + + private ArgumentCaptor<List<RemoteAction>> mRemoteActionListCaptor; + + private PipTaskListener mPipTaskListener; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mRemoteActionListCaptor = ArgumentCaptor.forClass(List.class); + when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(mMockLeash); + } + + @Test + public void constructor_addPipTransitionStateChangedListener() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + + verify(mMockPipTransitionState).addPipTransitionStateChangedListener(eq(mPipTaskListener)); + } + + @Test + public void setPictureInPictureParams_updatePictureInPictureParams() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + PictureInPictureParams params = mPipTaskListener.getPictureInPictureParams(); + assertEquals(aspectRatio, params.getAspectRatio()); + assertTrue(params.hasSetActions()); + assertEquals(1, params.getActions().size()); + assertEquals(action1, params.getActions().get(0).getTitle()); + } + + @Test + public void setPictureInPictureParams_withActionsChanged_callbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + action1 = "modified action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + verify(mMockPipParamsChangedCallback).onActionsChanged( + mRemoteActionListCaptor.capture(), any()); + assertEquals(1, mRemoteActionListCaptor.getValue().size()); + assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle()); + } + + @Test + public void setPictureInPictureParams_withoutActionsChanged_doesNotCallbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + String action1 = "action1"; + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + mPipTaskListener.setPictureInPictureParams(getPictureInPictureParams( + aspectRatio, action1)); + + verifyZeroInteractions(mMockPipParamsChangedCallback); + } + + @Test + public void onTaskInfoChanged_withActionsChanged_callbackActionsChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + clearInvocations(mMockPipBoundsState); + action1 = "modified action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verify(mMockPipTransitionState, times(0)) + .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + verify(mMockPipParamsChangedCallback).onActionsChanged( + mRemoteActionListCaptor.capture(), any()); + assertEquals(1, mRemoteActionListCaptor.getValue().size()); + assertEquals(action1, mRemoteActionListCaptor.getValue().get(0).getTitle()); + } + + @Test + public void onTaskInfoChanged_withAspectRatioChanged_callbackAspectRatioChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + clearInvocations(mMockPipBoundsState); + aspectRatio = new Rational(16, 9); + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verify(mMockPipTransitionState).setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + verifyZeroInteractions(mMockPipParamsChangedCallback); + } + + @Test + public void onTaskInfoChanged_withoutParamsChanged_doesNotCallbackAspectRatioChanged() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback); + Rational aspectRatio = new Rational(4, 3); + when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat()); + String action1 = "action1"; + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + clearInvocations(mMockPipParamsChangedCallback); + mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1)); + + verifyZeroInteractions(mMockPipParamsChangedCallback); + verify(mMockPipTransitionState, times(0)) + .setOnIdlePipTransitionStateRunnable(any(Runnable.class)); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChangeWithAspectRatioChange_schedule() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extras); + + verify(mMockPipScheduler).scheduleAnimateResizePip(any(), anyBoolean(), anyInt()); + } + + @Test + public void onPipTransitionStateChanged_scheduledBoundsChangeWithoutAspectRatioChange_noop() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + + verifyZeroInteractions(mMockPipScheduler); + } + + @Test + public void onPipTransitionStateChanged_changingPipBoundsWaitAspectRatioChange_animate() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, true); + extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS, + new Rect(0, 0, 100, 100)); + when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200)); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + mPipTaskListener.setPipResizeAnimatorSupplier( + (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds, + duration, delta) -> mMockPipResizeAnimator); + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + PipTransitionState.CHANGING_PIP_BOUNDS, + extras); + + verify(mMockPipResizeAnimator, times(1)).start(); + } + + @Test + public void onPipTransitionStateChanged_changingPipBoundsNotAspectRatioChange_noop() { + mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer, + mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState, + mMockPipBoundsAlgorithm, mMockShellExecutor); + Bundle extras = new Bundle(); + extras.putBoolean(ANIMATING_ASPECT_RATIO_CHANGE, false); + extras.putParcelable(PipTransition.PIP_DESTINATION_BOUNDS, + new Rect(0, 0, 100, 100)); + when(mMockPipBoundsState.getBounds()).thenReturn(new Rect(0, 0, 200, 200)); + + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.UNDEFINED, + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + extras); + mPipTaskListener.setPipResizeAnimatorSupplier( + (context, leash, startTx, finishTx, baseBounds, startBounds, endBounds, + duration, delta) -> mMockPipResizeAnimator); + mPipTaskListener.onPipTransitionStateChanged( + PipTransitionState.SCHEDULED_BOUNDS_CHANGE, + PipTransitionState.CHANGING_PIP_BOUNDS, + extras); + + verify(mMockPipResizeAnimator, times(0)).start(); + } + + private PictureInPictureParams getPictureInPictureParams(Rational aspectRatio, + String... actions) { + final PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); + builder.setAspectRatio(aspectRatio); + final List<RemoteAction> remoteActions = new ArrayList<>(); + for (String action : actions) { + remoteActions.add(new RemoteAction(mMockIcon, action, action, mMockPendingIntent)); + } + if (!remoteActions.isEmpty()) { + builder.setActions(remoteActions); + } + return builder.build(); + } + + private ActivityManager.RunningTaskInfo getTaskInfo(Rational aspectRatio, + String... actions) { + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.pictureInPictureParams = getPictureInPictureParams(aspectRatio, actions); + return taskInfo; + } +} |