Add a synchronous path when finishing a pre-existing recents animation

- If launcher repeatedly starts the recents animation, we try to finish
  the existing animation before starting the new one, but due to some
  ordering issues (see b/275561141) the subsequent starts can orphan
  the previous animation runner, which can result in no animation callbacks
  for either the previous animation (to cancel) or the new animation
  (to start).

  This change only attempts to reduce the likelihood of a second no-op
  transition by synchronously finishing the existing recents animation
  on the launcher side prior to starting the next animation.

  There is one case this doesn't handle, where if the previous
  onAnimationStart() has not been called back, then we can't directly
  call the controller to finish, and need to rely on the no-op handling
  on the shell transition side to handle the gesture.

Bug: 275561141
Test: Quickswitch and swipe up repeatedly
Change-Id: I820e26dc20fb1851ee0102ed8c114ce998d44999
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 4adfae5..f8e09e1 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -150,10 +150,17 @@
 
     @UiThread
     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
-        if (mFinishRequested) {
-            // If finishing, add to pending finish callbacks, otherwise, if finished, adding to the
-            // destroyed RunnableList will just trigger the callback to be called immediately
-            mPendingFinishCallbacks.add(callback);
+        finishController(toRecents, callback, sendUserLeaveHint, false /* forceFinish */);
+    }
+
+    @UiThread
+    public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint,
+            boolean forceFinish) {
+        mPendingFinishCallbacks.add(callback);
+        if (!forceFinish && mFinishRequested) {
+            // If finish has already been requested, then add the callback to the pending list.
+            // If already finished, then adding it to the destroyed RunnableList will just 
+            // trigger the callback to be called immediately
             return;
         }
         ActiveGestureLog.INSTANCE.addLog(
@@ -165,15 +172,19 @@
         mFinishRequested = true;
         mFinishTargetIsLauncher = toRecents;
         mOnFinishedListener.accept(this);
-        mPendingFinishCallbacks.add(callback);
-        UI_HELPER_EXECUTOR.execute(() -> {
+        Runnable finishCb = () -> {
             mController.finish(toRecents, sendUserLeaveHint);
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
             InteractionJankMonitorWrapper.end(
                     InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
             MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy);
-        });
+        };
+        if (forceFinish) {
+            finishCb.run();
+        } else {
+            UI_HELPER_EXECUTOR.execute(finishCb);
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index c3c1197..c8c6292 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -115,7 +115,7 @@
             }
         }
         // But force-finish it anyways
-        finishRunningRecentsAnimation(false /* toHome */);
+        finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */);
 
         if (mCallbacks != null) {
             // If mCallbacks still != null, that means we are getting this startRecentsAnimation()
@@ -291,13 +291,26 @@
      * Finishes the running recents animation.
      */
     public void finishRunningRecentsAnimation(boolean toHome) {
+        finishRunningRecentsAnimation(toHome, false /* forceFinish */);
+    }
+
+    /**
+     * Finishes the running recents animation.
+     * @param forceFinish will synchronously finish the controller
+     */
+    private void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
         if (mController != null) {
             ActiveGestureLog.INSTANCE.addLog(
                     /* event= */ "finishRunningRecentsAnimation", toHome);
             mCallbacks.notifyAnimationCanceled();
-            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
-                    ? mController::finishAnimationToHome
-                    : mController::finishAnimationToApp);
+            if (forceFinish) {
+                mController.finishController(toHome, null, false /* sendUserLeaveHint */,
+                        true /* forceFinish */);
+            } else {
+                Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
+                        ? mController::finishAnimationToHome
+                        : mController::finishAnimationToApp);
+            }
             cleanUpRecentsAnimation();
         }
     }