diff options
| author | 2024-03-26 08:00:57 +0000 | |
|---|---|---|
| committer | 2024-03-26 08:00:57 +0000 | |
| commit | 7d569c54b7efd05b4da9803abe5337b31418a4e5 (patch) | |
| tree | 175ceca2ca18854cd0bd9f387e0237cb227a7abf | |
| parent | b779f47e86d7e805a0d630edc79b2269c7760c9f (diff) | |
| parent | 95ba9d4c699a69cbcffe4f3316556212bb15ba84 (diff) | |
Merge "Play predictive animation when quickly restarting back gesture after cancel" into main
7 files changed, 148 insertions, 46 deletions
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java index 40e28cbbbd05..163e43a009e7 100644 --- a/core/java/android/window/BackProgressAnimator.java +++ b/core/java/android/window/BackProgressAnimator.java @@ -155,6 +155,14 @@ public class BackProgressAnimator { mSpring.animateToFinalPosition(0); } + /** + * Removes the finishCallback passed into {@link #onBackCancelled} + */ + public void removeOnBackCancelledFinishCallback() { + mSpring.removeEndListener(mOnAnimationEndListener); + mBackCancelledFinishRunnable = null; + } + /** Returns true if the back animation is in progress. */ boolean isBackAnimationInProgress() { return mBackAnimationInProgress; 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 9bd8531d33dc..9b9798c6d93b 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 @@ -147,7 +147,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private final Runnable mAnimationTimeoutRunnable = () -> { ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Animation didn't finish in %d ms. Resetting...", MAX_ANIMATION_DURATION); - onBackAnimationFinished(); + finishBackAnimation(); }; private IBackAnimationFinishedCallback mBackAnimationFinishedCallback; @@ -156,6 +156,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @Nullable private IOnBackInvokedCallback mActiveCallback; + @Nullable + private RemoteAnimationTarget[] mApps; @VisibleForTesting final RemoteCallback mNavigationObserver = new RemoteCallback( @@ -466,6 +468,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void onGestureStarted(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) { + boolean interruptCancelPostCommitAnimation = mPostCommitAnimationInProgress + && mCurrentTracker.isFinished() && !mCurrentTracker.getTriggerBack() + && mQueuedTracker.isInInitialState(); + if (interruptCancelPostCommitAnimation) { + // If a system animation is currently in the post-commit phase animating an + // onBackCancelled event, let's interrupt it and start animating a new back gesture + resetTouchTracker(); + } TouchTracker touchTracker; if (mCurrentTracker.isInInitialState()) { touchTracker = mCurrentTracker; @@ -480,9 +490,15 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont touchTracker.setState(TouchTracker.TouchTrackerState.ACTIVE); mBackGestureStarted = true; - if (touchTracker == mCurrentTracker) { + if (interruptCancelPostCommitAnimation) { + // post-commit cancel is currently running. let's interrupt it and dispatch a new + // onBackStarted event. + mPostCommitAnimationInProgress = false; + mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); + startSystemAnimation(); + } else if (touchTracker == mCurrentTracker) { // Only start the back navigation if no other gesture is being processed. Otherwise, - // the back navigation will be started once the current gesture has finished. + // the back navigation will fall back to legacy back event injection. startBackNavigation(mCurrentTracker); } } @@ -818,6 +834,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ @VisibleForTesting void onBackAnimationFinished() { + if (!mPostCommitAnimationInProgress) { + // This can happen when a post-commit cancel animation was interrupted by a new back + // gesture but the timing of interruption was bad such that the back-callback + // implementation finished in between the time of the new gesture having started and + // the time of the back-callback receiving the new onBackStarted event. Due to the + // asynchronous APIs this isn't an unlikely case. To handle this, let's return early. + // The back-callback implementation will call onBackAnimationFinished again when it is + // done with animating the second gesture. + return; + } + finishBackAnimation(); + } + + private void finishBackAnimation() { // Stop timeout runner. mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); mPostCommitAnimationInProgress = false; @@ -878,6 +908,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont void finishBackNavigation(boolean triggerBack) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()"); mActiveCallback = null; + mApps = null; mShouldStartOnNextMoveEvent = false; mOnBackStartDispatched = false; mPointerPilfered = false; @@ -914,6 +945,42 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTrackingLatency = false; } + private void startSystemAnimation() { + if (mBackNavigationInfo == null) { + ProtoLog.e(WM_SHELL_BACK_PREVIEW, "Lack of navigation info to start animation."); + return; + } + if (mApps == null) { + ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Not starting animation due to mApps being null."); + return; + } + + final BackAnimationRunner runner = + mShellBackAnimationRegistry.getAnimationRunnerAndInit(mBackNavigationInfo); + if (runner == null) { + if (mBackAnimationFinishedCallback != null) { + try { + mBackAnimationFinishedCallback.onAnimationFinished(false); + } catch (RemoteException e) { + Log.w(TAG, "Failed call IBackNaviAnimationController", e); + } + } + return; + } + mActiveCallback = runner.getCallback(); + + ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()"); + + runner.startAnimation(mApps, /*wallpapers*/ null, /*nonApps*/ null, + () -> mShellExecutor.execute(this::onBackAnimationFinished)); + + if (mApps.length >= 1) { + mCurrentTracker.updateStartLocation(); + BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]); + dispatchOnBackStarted(mActiveCallback, startEvent); + } + } + private void createAdapter() { IBackAnimationRunner runner = new IBackAnimationRunner.Stub() { @@ -926,48 +993,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor.execute( () -> { endLatencyTracking(); - if (mBackNavigationInfo == null) { - ProtoLog.e(WM_SHELL_BACK_PREVIEW, - "Lack of navigation info to start animation."); - return; - } - final BackAnimationRunner runner = - mShellBackAnimationRegistry.getAnimationRunnerAndInit( - mBackNavigationInfo); - if (runner == null) { - if (finishedCallback != null) { - try { - finishedCallback.onAnimationFinished(false); - } catch (RemoteException e) { - Log.w( - TAG, - "Failed call IBackNaviAnimationController", - e); - } - } - return; - } - mActiveCallback = runner.getCallback(); mBackAnimationFinishedCallback = finishedCallback; - - ProtoLog.d( - WM_SHELL_BACK_PREVIEW, - "BackAnimationController: startAnimation()"); - runner.startAnimation( - apps, - wallpapers, - nonApps, - () -> - mShellExecutor.execute( - BackAnimationController.this - ::onBackAnimationFinished)); - - if (apps.length >= 1) { - mCurrentTracker.updateStartLocation(); - BackMotionEvent startEvent = - mCurrentTracker.createStartEvent(apps[0]); - dispatchOnBackStarted(mActiveCallback, startEvent); - } + mApps = apps; + startSystemAnimation(); // Dispatch the first progress after animation start for // smoothing the initial animation, instead of waiting for next diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index edf29dd484fc..7561a266c5ec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -283,6 +283,11 @@ class CrossActivityBackAnimation @Inject constructor( private inner class Callback : IOnBackInvokedCallback.Default() { override fun onBackStarted(backMotionEvent: BackMotionEvent) { + // in case we're still animating an onBackCancelled event, let's remove the finish- + // callback from the progress animator to prevent calling finishAnimation() before + // restarting a new animation + progressAnimator.removeOnBackCancelledFinishCallback(); + startBackAnimation(backMotionEvent) progressAnimator.onBackStarted(backMotionEvent) { backEvent: BackEvent -> onGestureProgress(backEvent) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 4b3154190910..cfd9fb613414 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -275,8 +275,6 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { private void onGestureProgress(@NonNull BackEvent backEvent) { if (!mBackInProgress) { - mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT; - mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); mBackInProgress = true; } float progress = backEvent.getProgress(); @@ -326,6 +324,13 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { private final class Callback extends IOnBackInvokedCallback.Default { @Override public void onBackStarted(BackMotionEvent backEvent) { + // in case we're still animating an onBackCancelled event, let's remove the finish- + // callback from the progress animator to prevent calling finishAnimation() before + // restarting a new animation + mProgressAnimator.removeOnBackCancelledFinishCallback(); + + mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT; + mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); mProgressAnimator.onBackStarted(backEvent, CrossTaskBackAnimation.this::onGestureProgress); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java index 5254ff466123..fcf500a60166 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java @@ -285,6 +285,11 @@ public class CustomizeActivityAnimation extends ShellBackAnimation { private final class Callback extends IOnBackInvokedCallback.Default { @Override public void onBackStarted(BackMotionEvent backEvent) { + // in case we're still animating an onBackCancelled event, let's remove the finish- + // callback from the progress animator to prevent calling finishAnimation() before + // restarting a new animation + mProgressAnimator.removeOnBackCancelledFinishCallback(); + mProgressAnimator.onBackStarted(backEvent, CustomizeActivityAnimation.this::onGestureProgress); } 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 703eb199f260..2919782a758a 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 @@ -409,6 +409,32 @@ public class BackAnimationControllerTest extends ShellTestCase { } @Test + public void gestureNotQueued_WhenPreviousGestureIsPostCommitCancelling() + throws RemoteException { + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); + + doStartEvents(0, 100); + simulateRemoteAnimationStart(); + releaseBackGesture(); + + // Check that back cancellation is dispatched. + verify(mAnimatorCallback).onBackCancelled(); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); + + reset(mAnimatorCallback); + reset(mBackAnimationRunner); + + // Verify that a new start event is dispatched if a new gesture is started during the + // post-commit cancel phase + triggerBackGesture(); + verify(mAnimatorCallback).onBackStarted(any()); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); + } + + @Test public void acceptsGesture_transitionTimeout() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 7e26577e96d4..8932e60048e6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -134,6 +134,31 @@ public class BackProgressAnimatorTest { assertEquals(0, cancelCallbackCalled.getCount()); } + @Test + public void testCancelFinishCallbackNotInvokedWhenRemoved() throws InterruptedException { + // Give the animator some progress. + final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress); + mMainThreadHandler.post( + () -> mProgressAnimator.onBackProgressed(backEvent)); + mTargetProgressCalled.await(1, TimeUnit.SECONDS); + assertNotNull(mReceivedBackEvent); + + // call onBackCancelled (which animates progress to 0 before invoking the finishCallback) + CountDownLatch finishCallbackCalled = new CountDownLatch(1); + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> mProgressAnimator.onBackCancelled(finishCallbackCalled::countDown)); + + // remove onBackCancelled finishCallback (while progress is still animating to 0) + InstrumentationRegistry.getInstrumentation().runOnMainSync( + () -> mProgressAnimator.removeOnBackCancelledFinishCallback()); + + // call reset (which triggers the finishCallback invocation, if one is present) + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mProgressAnimator.reset()); + + // verify that finishCallback is not invoked + assertEquals(1, finishCallbackCalled.getCount()); + } + private void onGestureProgress(BackEvent backEvent) { if (mTargetProgress == backEvent.getProgress()) { mReceivedBackEvent = backEvent; |