Loading libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +41 −1 Original line number Diff line number Diff line Loading @@ -58,6 +58,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @VisibleForTesting boolean mEnableAnimations = SystemProperties.getInt( "persist.wm.debug.predictive_back_anim", 0) != 0; /** * Max duration to wait for a transition to finish before accepting another gesture start * request. */ private static final long MAX_TRANSITION_DURATION = 2000; /** * Location of the initial touch event of the back gesture. Loading @@ -73,6 +78,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont /** True when a back gesture is ongoing */ private boolean mBackGestureStarted = false; /** Tracks if an uninterruptible transition is in progress */ private boolean mTransitionInProgress = false; /** @see #setTriggerBack(boolean) */ private boolean mTriggerBack; Loading @@ -85,6 +92,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private IOnBackInvokedCallback mBackToLauncherCallback; private float mTriggerThreshold; private float mProgressThreshold; private final Runnable mResetTransitionRunnable = () -> { finishAnimation(); mTransitionInProgress = false; ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...", MAX_TRANSITION_DURATION); }; public BackAnimationController( @ShellMainThread ShellExecutor shellExecutor, Loading Loading @@ -189,7 +202,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mBackToLauncherCallback = null; } private void onBackToLauncherAnimationFinished() { @VisibleForTesting void onBackToLauncherAnimationFinished() { if (mBackNavigationInfo != null) { IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); if (mTriggerBack) { Loading @@ -206,6 +220,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * {@link BackAnimationController} */ public void onMotionEvent(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) { if (mTransitionInProgress) { return; } if (action == MotionEvent.ACTION_MOVE) { if (!mBackGestureStarted) { // Let the animation initialized here to make sure the onPointerDownOutsideFocus Loading Loading @@ -330,6 +347,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher ? mBackToLauncherCallback : mBackNavigationInfo.getOnBackInvokedCallback(); if (shouldDispatchToLauncher) { startTransition(); } if (mTriggerBack) { dispatchOnBackInvoked(targetCallback); } else { Loading Loading @@ -401,6 +421,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Sets to true when the back gesture has passed the triggering threshold, false otherwise. */ public void setTriggerBack(boolean triggerBack) { if (mTransitionInProgress) { return; } mTriggerBack = triggerBack; } Loading Loading @@ -432,6 +455,23 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransaction.remove(screenshotSurface); } mTransaction.apply(); stopTransition(); backNavigationInfo.onBackNavigationFinished(triggerBack); } private void startTransition() { if (mTransitionInProgress) { return; } mTransitionInProgress = true; mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION); } private void stopTransition() { if (!mTransitionInProgress) { return; } mShellExecutor.removeCallbacks(mResetTransitionRunnable); mTransitionInProgress = false; } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +59 −2 Original line number Diff line number Diff line Loading @@ -26,7 +26,9 @@ import static org.mockito.Mockito.atLeastOnce; 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.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.IActivityTaskManager; import android.app.WindowConfiguration; Loading @@ -47,7 +49,6 @@ import android.window.IOnBackInvokedCallback; import androidx.test.filters.SmallTest; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import org.junit.Before; import org.junit.Ignore; Loading @@ -64,7 +65,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class BackAnimationControllerTest { private final ShellExecutor mShellExecutor = new TestShellExecutor(); private final TestShellExecutor mShellExecutor = new TestShellExecutor(); @Mock private Context mContext; Loading Loading @@ -221,4 +222,60 @@ public class BackAnimationControllerTest { BackEvent.EDGE_LEFT); verify(mIOnBackInvokedCallback).onBackInvoked(); } @Test public void ignoresGesture_transitionInProgress() throws RemoteException { mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, null, null, BackNavigationInfo.TYPE_RETURN_TO_HOME); triggerBackGesture(); // Check that back invocation is dispatched. verify(mIOnBackInvokedCallback).onBackInvoked(); reset(mIOnBackInvokedCallback); // Verify that we prevent animation from restarting if another gestures happens before // the previous transition is finished. mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), MotionEvent.ACTION_DOWN, BackEvent.EDGE_LEFT); verifyNoMoreInteractions(mIOnBackInvokedCallback); // Verify that we start accepting gestures again once transition finishes. mController.onBackToLauncherAnimationFinished(); mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), MotionEvent.ACTION_DOWN, BackEvent.EDGE_LEFT); mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 100, 100, 0), MotionEvent.ACTION_MOVE, BackEvent.EDGE_LEFT); verify(mIOnBackInvokedCallback).onBackStarted(); } @Test public void acceptsGesture_transitionTimeout() throws RemoteException { mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, null, null, BackNavigationInfo.TYPE_RETURN_TO_HOME); triggerBackGesture(); reset(mIOnBackInvokedCallback); // Simulate transition timeout. mShellExecutor.flushAll(); mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), MotionEvent.ACTION_DOWN, BackEvent.EDGE_LEFT); mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 100, 100, 0), MotionEvent.ACTION_MOVE, BackEvent.EDGE_LEFT); verify(mIOnBackInvokedCallback).onBackStarted(); } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +41 −1 Original line number Diff line number Diff line Loading @@ -58,6 +58,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @VisibleForTesting boolean mEnableAnimations = SystemProperties.getInt( "persist.wm.debug.predictive_back_anim", 0) != 0; /** * Max duration to wait for a transition to finish before accepting another gesture start * request. */ private static final long MAX_TRANSITION_DURATION = 2000; /** * Location of the initial touch event of the back gesture. Loading @@ -73,6 +78,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont /** True when a back gesture is ongoing */ private boolean mBackGestureStarted = false; /** Tracks if an uninterruptible transition is in progress */ private boolean mTransitionInProgress = false; /** @see #setTriggerBack(boolean) */ private boolean mTriggerBack; Loading @@ -85,6 +92,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private IOnBackInvokedCallback mBackToLauncherCallback; private float mTriggerThreshold; private float mProgressThreshold; private final Runnable mResetTransitionRunnable = () -> { finishAnimation(); mTransitionInProgress = false; ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...", MAX_TRANSITION_DURATION); }; public BackAnimationController( @ShellMainThread ShellExecutor shellExecutor, Loading Loading @@ -189,7 +202,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mBackToLauncherCallback = null; } private void onBackToLauncherAnimationFinished() { @VisibleForTesting void onBackToLauncherAnimationFinished() { if (mBackNavigationInfo != null) { IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback(); if (mTriggerBack) { Loading @@ -206,6 +220,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * {@link BackAnimationController} */ public void onMotionEvent(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) { if (mTransitionInProgress) { return; } if (action == MotionEvent.ACTION_MOVE) { if (!mBackGestureStarted) { // Let the animation initialized here to make sure the onPointerDownOutsideFocus Loading Loading @@ -330,6 +347,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher ? mBackToLauncherCallback : mBackNavigationInfo.getOnBackInvokedCallback(); if (shouldDispatchToLauncher) { startTransition(); } if (mTriggerBack) { dispatchOnBackInvoked(targetCallback); } else { Loading Loading @@ -401,6 +421,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Sets to true when the back gesture has passed the triggering threshold, false otherwise. */ public void setTriggerBack(boolean triggerBack) { if (mTransitionInProgress) { return; } mTriggerBack = triggerBack; } Loading Loading @@ -432,6 +455,23 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransaction.remove(screenshotSurface); } mTransaction.apply(); stopTransition(); backNavigationInfo.onBackNavigationFinished(triggerBack); } private void startTransition() { if (mTransitionInProgress) { return; } mTransitionInProgress = true; mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION); } private void stopTransition() { if (!mTransitionInProgress) { return; } mShellExecutor.removeCallbacks(mResetTransitionRunnable); mTransitionInProgress = false; } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +59 −2 Original line number Diff line number Diff line Loading @@ -26,7 +26,9 @@ import static org.mockito.Mockito.atLeastOnce; 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.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.app.IActivityTaskManager; import android.app.WindowConfiguration; Loading @@ -47,7 +49,6 @@ import android.window.IOnBackInvokedCallback; import androidx.test.filters.SmallTest; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; import org.junit.Before; import org.junit.Ignore; Loading @@ -64,7 +65,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class BackAnimationControllerTest { private final ShellExecutor mShellExecutor = new TestShellExecutor(); private final TestShellExecutor mShellExecutor = new TestShellExecutor(); @Mock private Context mContext; Loading Loading @@ -221,4 +222,60 @@ public class BackAnimationControllerTest { BackEvent.EDGE_LEFT); verify(mIOnBackInvokedCallback).onBackInvoked(); } @Test public void ignoresGesture_transitionInProgress() throws RemoteException { mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, null, null, BackNavigationInfo.TYPE_RETURN_TO_HOME); triggerBackGesture(); // Check that back invocation is dispatched. verify(mIOnBackInvokedCallback).onBackInvoked(); reset(mIOnBackInvokedCallback); // Verify that we prevent animation from restarting if another gestures happens before // the previous transition is finished. mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), MotionEvent.ACTION_DOWN, BackEvent.EDGE_LEFT); verifyNoMoreInteractions(mIOnBackInvokedCallback); // Verify that we start accepting gestures again once transition finishes. mController.onBackToLauncherAnimationFinished(); mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), MotionEvent.ACTION_DOWN, BackEvent.EDGE_LEFT); mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 100, 100, 0), MotionEvent.ACTION_MOVE, BackEvent.EDGE_LEFT); verify(mIOnBackInvokedCallback).onBackStarted(); } @Test public void acceptsGesture_transitionTimeout() throws RemoteException { mController.setBackToLauncherCallback(mIOnBackInvokedCallback); RemoteAnimationTarget animationTarget = createAnimationTarget(); createNavigationInfo(animationTarget, null, null, BackNavigationInfo.TYPE_RETURN_TO_HOME); triggerBackGesture(); reset(mIOnBackInvokedCallback); // Simulate transition timeout. mShellExecutor.flushAll(); mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0), MotionEvent.ACTION_DOWN, BackEvent.EDGE_LEFT); mController.onMotionEvent( MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 100, 100, 0), MotionEvent.ACTION_MOVE, BackEvent.EDGE_LEFT); verify(mIOnBackInvokedCallback).onBackStarted(); } }