diff options
2 files changed, 223 insertions, 4 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 12422874ca5d..a0a9451a0391 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -146,7 +146,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final Handler mBgHandler; private final WindowManager mWindowManager; private final Transitions mTransitions; - private final BackTransitionHandler mBackTransitionHandler; + @VisibleForTesting + final BackTransitionHandler mBackTransitionHandler; @VisibleForTesting final Rect mTouchableArea = new Rect(); @@ -174,7 +175,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Nullable private IOnBackInvokedCallback mActiveCallback; @Nullable - private RemoteAnimationTarget[] mApps; + @VisibleForTesting + RemoteAnimationTarget[] mApps; @VisibleForTesting final RemoteCallback mNavigationObserver = new RemoteCallback( @@ -1448,7 +1450,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Check whether this transition is prepare for predictive back animation, which could * happen when core make an activity become visible. */ - private boolean handlePrepareTransition( + @VisibleForTesting + boolean handlePrepareTransition( @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction st, @NonNull SurfaceControl.Transaction ft, @@ -1491,7 +1494,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Check whether this transition is triggered from back gesture commitment. * Reparent the transition targets to animation leashes, so the animation won't be broken. */ - private boolean handleCloseTransition(@NonNull TransitionInfo info, + @VisibleForTesting + boolean handleCloseTransition(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction st, @NonNull SurfaceControl.Transaction ft, @NonNull Transitions.TransitionFinishCallback finishCallback) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 56fad952dc31..311b1c5ffd21 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -16,8 +16,19 @@ package com.android.wm.shell.back; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; import static android.window.BackNavigationInfo.KEY_NAVIGATION_FINISHED; +import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; +import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -25,6 +36,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -32,6 +44,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.app.ActivityManager; import android.app.IActivityTaskManager; import android.app.WindowConfiguration; import android.content.pm.ApplicationInfo; @@ -40,6 +53,7 @@ import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; import android.provider.Settings; @@ -51,11 +65,16 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.BackEvent; import android.window.BackMotionEvent; import android.window.BackNavigationInfo; import android.window.IBackAnimationFinishedCallback; import android.window.IOnBackInvokedCallback; +import android.window.IWindowContainerToken; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerToken; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -128,6 +147,8 @@ public class BackAnimationControllerTest extends ShellTestCase { private ShellBackAnimationRegistry mShellBackAnimationRegistry; private Rect mTouchableRegion; + private BackAnimationController.BackTransitionHandler mBackTransitionHandler; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -165,6 +186,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellExecutor.flushAll(); mTouchableRegion = new Rect(0, 0, 100, 100); mController.mTouchableArea.set(mTouchableRegion); + mBackTransitionHandler = mController.mBackTransitionHandler; + spyOn(mBackTransitionHandler); } private void createNavigationInfo(int backType, @@ -606,6 +629,198 @@ public class BackAnimationControllerTest extends ShellTestCase { mCrossTaskBackAnimation.getRunner()); } + @Test + public void testCloseAsExpectTransition() { + final int openTaskId = 1; + final int closeTaskId = 2; + mController.mApps = createAppAnimationTargets(openTaskId, closeTaskId); + final IBinder mockBinder = mock(IBinder.class); + final SurfaceControl.Transaction st = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction ft = mock(SurfaceControl.Transaction.class); + // Single close + final TransitionInfo.Change open = createAppChange(openTaskId, TRANSIT_OPEN, + FLAG_BACK_GESTURE_ANIMATED | FLAG_MOVED_TO_TOP); + final TransitionInfo.Change close = createAppChange(closeTaskId, TRANSIT_CLOSE, + FLAG_BACK_GESTURE_ANIMATED); + + TransitionInfo tInfo = createTransitionInfo(TRANSIT_CLOSE, open, close); + mBackTransitionHandler.mCloseTransitionRequested = true; + Transitions.TransitionFinishCallback callback = + mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback); + verify(mBackTransitionHandler).handleCloseTransition( + eq(tInfo), eq(st), eq(ft), eq(callback)); + mBackTransitionHandler.onAnimationFinished(); + verify(callback).onTransitionFinished(any()); + mBackTransitionHandler.mCloseTransitionRequested = false; + + // PREPARE + CLOSE + tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open); + callback = mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback); + verify(mBackTransitionHandler).handlePrepareTransition( + eq(tInfo), eq(st), eq(ft), eq(callback)); + mBackTransitionHandler.mCloseTransitionRequested = true; + TransitionInfo tInfo2 = createTransitionInfo(TRANSIT_CLOSE, close); + Transitions.TransitionFinishCallback mergeCallback = + mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.mergeAnimation( + mock(IBinder.class), tInfo2, st, mock(IBinder.class), mergeCallback); + mBackTransitionHandler.onAnimationFinished(); + verify(callback).onTransitionFinished(any()); + verify(mergeCallback).onTransitionFinished(any()); + mBackTransitionHandler.mCloseTransitionRequested = false; + + // PREPARE contains close info + tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open, close); + callback = mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.mCloseTransitionRequested = true; + mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback); + verify(mBackTransitionHandler).handleCloseTransition( + eq(tInfo), eq(st), eq(ft), eq(callback)); + mBackTransitionHandler.onAnimationFinished(); + verify(callback).onTransitionFinished(any()); + mBackTransitionHandler.mCloseTransitionRequested = false; + + // PREPARE then Cancel + tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open); + callback = mock(Transitions.TransitionFinishCallback.class); + final TransitionRequestInfo requestInfo = new TransitionRequestInfo( + TRANSIT_PREPARE_BACK_NAVIGATION, null /* triggerTask */, + null /* remoteTransition */); + mBackTransitionHandler.handleRequest(mockBinder, requestInfo); + mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback); + verify(mBackTransitionHandler).handlePrepareTransition( + eq(tInfo), eq(st), eq(ft), eq(callback)); + final TransitionInfo.Change openToClose = createAppChange(openTaskId, TRANSIT_CLOSE, + FLAG_BACK_GESTURE_ANIMATED); + tInfo2 = createTransitionInfo(TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, openToClose); + mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class); + mergeCallback = mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.mergeAnimation(mBackTransitionHandler.mClosePrepareTransition, + tInfo2, st, mock(IBinder.class), mergeCallback); + assertTrue("Change should be consumed", tInfo2.getChanges().isEmpty()); + mBackTransitionHandler.onAnimationFinished(); + verify(callback).onTransitionFinished(any()); + } + + @Test + public void testCancelUnexpectedTransition() { + final int openTaskId = 1; + final int closeTaskId = 2; + mController.mApps = createAppAnimationTargets(openTaskId, closeTaskId); + final IBinder mockBinder = mock(IBinder.class); + final SurfaceControl.Transaction st = mock(SurfaceControl.Transaction.class); + final SurfaceControl.Transaction ft = mock(SurfaceControl.Transaction.class); + final TransitionInfo.Change open = createAppChange(openTaskId, TRANSIT_OPEN, + FLAG_BACK_GESTURE_ANIMATED | FLAG_MOVED_TO_TOP); + final TransitionInfo.Change close = createAppChange(closeTaskId, TRANSIT_CLOSE, + FLAG_BACK_GESTURE_ANIMATED); + + // Didn't trigger close transition + mBackTransitionHandler.mCloseTransitionRequested = false; + TransitionInfo prepareInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, + open, close); + final Transitions.TransitionFinishCallback callback = + mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.handleRequest(mockBinder, mock(TransitionRequestInfo.class)); + boolean canHandle = mBackTransitionHandler.startAnimation( + mockBinder, prepareInfo, st, ft, callback); + assertFalse("Should not handle transition", canHandle); + assertNull(mBackTransitionHandler.mOnAnimationFinishCallback); + + // Didn't trigger close transition, but receive close target. + final TransitionRequestInfo requestInfo = new TransitionRequestInfo( + TRANSIT_PREPARE_BACK_NAVIGATION, null /* triggerTask */, + null /* remoteTransition */); + prepareInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open); + final Transitions.TransitionFinishCallback callback2 = + mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.handleRequest(mockBinder, requestInfo); + canHandle = mBackTransitionHandler.startAnimation(mockBinder, + prepareInfo, st, ft, callback2); + assertTrue("Handle prepare transition" , canHandle); + verify(mBackTransitionHandler).handlePrepareTransition( + eq(prepareInfo), eq(st), eq(ft), eq(callback2)); + final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close); + Transitions.TransitionFinishCallback mergeCallback = + mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, ft, + mock(IBinder.class), mergeCallback); + verify(callback2).onTransitionFinished(any()); + verify(mergeCallback, never()).onTransitionFinished(any()); + + // Didn't trigger close transition, but contains open target. + final int openTaskId2 = 3; + final Transitions.TransitionFinishCallback callback3 = + mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.handleRequest(mockBinder, requestInfo); + canHandle = mBackTransitionHandler.startAnimation( + mockBinder, prepareInfo, st, ft, callback3); + assertTrue("Handle prepare transition" , canHandle); + verify(mBackTransitionHandler).handlePrepareTransition( + eq(prepareInfo), eq(st), eq(ft), eq(callback3)); + final TransitionInfo.Change open2 = createAppChange( + openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP); + final TransitionInfo openInfo = createTransitionInfo(TRANSIT_OPEN, open2, close); + mergeCallback = mock(Transitions.TransitionFinishCallback.class); + mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, ft, + mock(IBinder.class), mergeCallback); + verify(callback3).onTransitionFinished(any()); + verify(mergeCallback, never()).onTransitionFinished(any()); + } + + private RemoteAnimationTarget[] createAppAnimationTargets(int openTaskId, int closeTaskId) { + final RemoteAnimationTarget openT = createSingleAnimationTarget(openTaskId, + RemoteAnimationTarget.MODE_OPENING); + final RemoteAnimationTarget closeT = createSingleAnimationTarget(closeTaskId, + RemoteAnimationTarget.MODE_CLOSING); + return new RemoteAnimationTarget[]{openT, closeT}; + } + + private RemoteAnimationTarget createSingleAnimationTarget(int taskId, int mode) { + final Rect fakeR = new Rect(); + final Point fakeP = new Point(); + final ActivityManager.RunningTaskInfo openTaskInfo = new ActivityManager.RunningTaskInfo(); + openTaskInfo.taskId = taskId; + openTaskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class)); + return new RemoteAnimationTarget( + taskId, mode, mock(SurfaceControl.class), false, fakeR, fakeR, + 0, fakeP, fakeR, fakeR, new WindowConfiguration(), false, + mock(SurfaceControl.class), fakeR, openTaskInfo, false); + } + private TransitionInfo.Change createAppChange( + int taskId, @TransitionInfo.TransitionMode int mode, + @TransitionInfo.ChangeFlags int flags) { + final TransitionInfo.Change change; + SurfaceControl.Builder b = new SurfaceControl.Builder() + .setName("test task"); + if (taskId != INVALID_TASK_ID) { + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class)); + change = new TransitionInfo.Change( + taskInfo.token, b.build()); + change.setTaskInfo(taskInfo); + } else { + change = new TransitionInfo.Change( + null, b.build()); + + } + change.setMode(mode); + change.setFlags(flags); + return change; + } + + private static TransitionInfo createTransitionInfo( + @WindowManager.TransitionType int type, TransitionInfo.Change ... changes) { + final TransitionInfo info = new TransitionInfo(type, 0); + for (int i = 0; i < changes.length; ++i) { + info.addChange(changes[i]); + } + return info; + } + private void verifySystemBackBehavior(int type, BackAnimationRunner animation) throws RemoteException { final BackAnimationRunner animationRunner = spy(animation); |