Reland slow gesture callback handling

Now that b/329324086 has been fixed, we can be more certain that launcher always gets a signal to clean up from WM.

- Relanding original fix for b/285636175 with some additional error checking
- We will now check whether the recents animation start is pending on ACTION_UP
- We will now block entire swipes to prevent sending additional inputs an input consumer while the recents animation start is pending
- We will now only stop blocking inputs on ACTION_DOWN

Flag: LEGACY ENABLE_HANDLE_DELAYED_GESTURE_CALLBACKS TEAMFOOD
Bug: 329324927
Fixes: 285636175
Test: added a delay in RecentsAnimationCallbacks.onAnimationStart and attempted several rapid gestures
Change-Id: I9805114da34bf44e6b28c2a8a665e4cca88904c2
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 8274bd6..6b07bb6 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -229,3 +229,13 @@
     description: "Enables an add button in the widget picker"
     bug: "323886237"
 }
+
+flag {
+  name: "enable_handle_delayed_gesture_callbacks"
+  namespace: "launcher"
+  description: "Enables additional handling for delayed mid-gesture callbacks"
+  bug: "285636175"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 6b33c0a..bac9740 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -516,13 +516,14 @@
         return mSwipeUpStartTimeMs;
     }
 
-    public void dump(PrintWriter pw) {
-        pw.println("GestureState:");
-        pw.println("  gestureID=" + mGestureId);
-        pw.println("  runningTask=" + mRunningTask);
-        pw.println("  endTarget=" + mEndTarget);
-        pw.println("  lastAppearedTaskTargetId=" + Arrays.toString(mLastAppearedTaskTargets));
-        pw.println("  lastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
-        pw.println("  isRecentsAnimationRunning=" + isRecentsAnimationRunning());
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "GestureState:");
+        pw.println(prefix + "\tgestureID=" + mGestureId);
+        pw.println(prefix + "\trunningTask=" + mRunningTask);
+        pw.println(prefix + "\tendTarget=" + mEndTarget);
+        pw.println(prefix + "\tlastAppearedTaskTargetId="
+                + Arrays.toString(mLastAppearedTaskTargets));
+        pw.println(prefix + "\tlastStartedTaskId=" + Arrays.toString(mLastStartedTaskId));
+        pw.println(prefix + "\tisRecentsAnimationRunning=" + isRecentsAnimationRunning());
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 5d26ec0..da7a98f 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -39,6 +39,7 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -219,6 +220,13 @@
         }
     }
 
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "RecentsAnimationCallbacks:");
+
+        pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
+        pw.println(prefix + "\tmCancelled=" + mCancelled);
+    }
+
     /**
      * Listener for the recents animation callbacks.
      */
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 06a442b..1b05e28 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -41,6 +41,7 @@
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 
+import java.io.PrintWriter;
 import java.util.function.Consumer;
 
 /**
@@ -267,4 +268,14 @@
     public boolean getFinishTargetIsLauncher() {
         return mFinishTargetIsLauncher;
     }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "RecentsAnimationController:");
+
+        pw.println(prefix + "\tmAllowMinimizeSplitScreen=" + mAllowMinimizeSplitScreen);
+        pw.println(prefix + "\tmUseLauncherSysBarFlags=" + mUseLauncherSysBarFlags);
+        pw.println(prefix + "\tmSplitScreenMinimized=" + mSplitScreenMinimized);
+        pw.println(prefix + "\tmFinishRequested=" + mFinishRequested);
+        pw.println(prefix + "\tmFinishTargetIsLauncher=" + mFinishTargetIsLauncher);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index f936882..82bb453 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -25,6 +25,8 @@
 import android.os.Bundle;
 import android.view.RemoteAnimationTarget;
 
+import java.io.PrintWriter;
+
 /**
  * Extension of {@link RemoteAnimationTargets} with additional information about swipe
  * up animation
@@ -63,4 +65,14 @@
         }
         return false;
     }
+
+    @Override
+    public void dump(String prefix, PrintWriter pw) {
+        super.dump(prefix, pw);
+        prefix += '\t';
+        pw.println(prefix + "RecentsAnimationTargets:");
+
+        pw.println(prefix + "\thomeContentInsets=" + homeContentInsets);
+        pw.println(prefix + "\tminimizedHomeBounds=" + minimizedHomeBounds);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index e0c7403..57edd82 100644
--- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.view.RemoteAnimationTarget;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -149,6 +150,14 @@
         }
     }
 
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "RemoteAnimationTargets:");
+
+        pw.println(prefix + "\ttargetMode=" + targetMode);
+        pw.println(prefix + "\thasRecents=" + hasRecents);
+        pw.println(prefix + "\tmReleased=" + mReleased);
+    }
+
     /**
      * Interface for intercepting surface release method
      */
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 3b1ed46..dec8a12 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -17,6 +17,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 
+import static com.android.launcher3.Flags.enableHandleDelayedGestureCallbacks;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
@@ -49,6 +50,7 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
+import java.io.PrintWriter;
 import java.util.HashMap;
 
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
@@ -67,6 +69,7 @@
     private Context mCtx;
 
     private boolean mRecentsAnimationStartPending = false;
+    private boolean mShouldIgnoreMotionEvents = false;
 
     private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
         @Override
@@ -103,8 +106,16 @@
                 .startRecentsActivity(intent, 0, null, null, null));
     }
 
-    public boolean isRecentsAnimationStartPending() {
-        return mRecentsAnimationStartPending;
+    boolean shouldIgnoreMotionEvents() {
+        return mShouldIgnoreMotionEvents;
+    }
+
+    void notifyNewGestureStart() {
+        // If mRecentsAnimationStartPending is true at the beginning of a gesture, block all motion
+        // events for this new gesture so that this new gesture does not interfere with the
+        // previously-requested recents animation. Otherwise, clean up mShouldIgnoreMotionEvents.
+        // NOTE: this can lead to misleading logs
+        mShouldIgnoreMotionEvents = mRecentsAnimationStartPending;
     }
 
     /**
@@ -145,7 +156,12 @@
             @Override
             public void onRecentsAnimationStart(RecentsAnimationController controller,
                     RecentsAnimationTargets targets) {
-                mRecentsAnimationStartPending = false;
+                if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+                    ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                            "TaskAnimationManager.startRecentsAnimation(onRecentsAnimationStart): ")
+                            .append("Setting mRecentsAnimationStartPending = false"));
+                    mRecentsAnimationStartPending = false;
+                }
                 if (mCallbacks == null) {
                     // It's possible for the recents animation to have finished and be cleaned up
                     // by the time we process the start callback, and in that case, just we can skip
@@ -186,13 +202,25 @@
 
             @Override
             public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
-                mRecentsAnimationStartPending = false;
+                if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+                    ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                            "TaskAnimationManager.startRecentsAnimation")
+                            .append("(onRecentsAnimationCanceled): ")
+                            .append("Setting mRecentsAnimationStartPending = false"));
+                    mRecentsAnimationStartPending = false;
+                }
                 cleanUpRecentsAnimation(newCallbacks);
             }
 
             @Override
             public void onRecentsAnimationFinished(RecentsAnimationController controller) {
-                mRecentsAnimationStartPending = false;
+                if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
+                    ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                            "TaskAnimationManager.startRecentsAnimation")
+                            .append("(onRecentsAnimationFinished): ")
+                            .append("Setting mRecentsAnimationStartPending = false"));
+                    mRecentsAnimationStartPending = false;
+                }
                 cleanUpRecentsAnimation(newCallbacks);
             }
 
@@ -303,13 +331,29 @@
             options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
             mRecentsAnimationStartPending = SystemUiProxy.INSTANCE.getNoCreate()
                     .startRecentsActivity(intent, options, mCallbacks);
+            if (enableHandleDelayedGestureCallbacks()) {
+                ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                        "TaskAnimationManager.startRecentsAnimation(shell transition path): ")
+                        .append("Setting mRecentsAnimationStartPending = ")
+                        .append(mRecentsAnimationStartPending));
+            }
         } else {
             UI_HELPER_EXECUTOR.execute(
                     () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
                             intent,
                             eventTime,
                             mCallbacks,
-                            result -> mRecentsAnimationStartPending = result,
+                            result -> {
+                                if (enableHandleDelayedGestureCallbacks()) {
+                                    ActiveGestureLog.INSTANCE.addLog(
+                                            new ActiveGestureLog.CompoundString(
+                                                    "TaskAnimationManager.startRecentsAnimation")
+                                                    .append("(legacy path): Setting ")
+                                                    .append("mRecentsAnimationStartPending = ")
+                                                    .append(result));
+                                }
+                                mRecentsAnimationStartPending = result;
+                            },
                             MAIN_EXECUTOR.getHandler()));
         }
         gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
@@ -439,7 +483,24 @@
         return mCallbacks;
     }
 
-    public void dump() {
-        // TODO
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "TaskAnimationManager:");
+
+        if (enableHandleDelayedGestureCallbacks()) {
+            pw.println(prefix + "\tmRecentsAnimationStartPending=" + mRecentsAnimationStartPending);
+            pw.println(prefix + "\tmShouldIgnoreUpcomingGestures=" + mShouldIgnoreMotionEvents);
+        }
+        if (mController != null) {
+            mController.dump(prefix + '\t', pw);
+        }
+        if (mCallbacks != null) {
+            mCallbacks.dump(prefix + '\t', pw);
+        }
+        if (mTargets != null) {
+            mTargets.dump(prefix + '\t', pw);
+        }
+        if (mLastGestureState != null) {
+            mLastGestureState.dump(prefix + '\t', pw);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 7035d6e..832ae0f 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -24,6 +24,7 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.enableHandleDelayedGestureCallbacks;
 import static com.android.launcher3.Flags.useActivityOverlay;
 import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE;
 import static com.android.launcher3.LauncherPrefs.backedUpItem;
@@ -41,6 +42,7 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
@@ -716,16 +718,22 @@
         boolean isHoverActionWithoutConsumer = enableCursorHoverStates()
                 && isHoverActionWithoutConsumer(event);
 
-        // TODO(b/285636175): Uncomment this once WM can properly guarantee all animation callbacks
-//        if (mTaskAnimationManager.isRecentsAnimationStartPending()
-//                && (action == ACTION_DOWN || isHoverActionWithoutConsumer)) {
-//            ActiveGestureLog.INSTANCE.addLog(
-//                    new CompoundString("TIS.onInputEvent: ")
-//                            .append("Cannot process input event: a recents animation has been ")
-//                            .append("requested, but hasn't started."),
-//                    RECENTS_ANIMATION_START_PENDING);
-//            return;
-//        }
+        if (enableHandleDelayedGestureCallbacks()) {
+            if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+                mTaskAnimationManager.notifyNewGestureStart();
+            }
+            if (mTaskAnimationManager.shouldIgnoreMotionEvents()) {
+                if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+                    ActiveGestureLog.INSTANCE.addLog(
+                            new CompoundString("TIS.onMotionEvent: A new gesture has been ")
+                                    .append("started, but a previously-requested recents ")
+                                    .append("animation hasn't started. Ignoring all following ")
+                                    .append("motion events."),
+                            RECENTS_ANIMATION_START_PENDING);
+                }
+                return;
+            }
+        }
 
         SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
 
@@ -1431,28 +1439,31 @@
             mOverviewCommandHelper.dump(pw);
         }
         if (mGestureState != null) {
-            mGestureState.dump(pw);
+            mGestureState.dump("", pw);
         }
         pw.println("Input state:");
-        pw.println("  mInputMonitorCompat=" + mInputMonitorCompat);
-        pw.println("  mInputEventReceiver=" + mInputEventReceiver);
+        pw.println("\tmInputMonitorCompat=" + mInputMonitorCompat);
+        pw.println("\tmInputEventReceiver=" + mInputEventReceiver);
         DisplayController.INSTANCE.get(this).dump(pw);
         pw.println("TouchState:");
         BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
                 : mOverviewComponentObserver.getActivityInterface().getCreatedContainer();
         boolean resumed = mOverviewComponentObserver != null
                 && mOverviewComponentObserver.getActivityInterface().isResumed();
-        pw.println("  createdOverviewActivity=" + createdOverviewActivity);
-        pw.println("  resumed=" + resumed);
-        pw.println("  mConsumer=" + mConsumer.getName());
+        pw.println("\tcreatedOverviewActivity=" + createdOverviewActivity);
+        pw.println("\tresumed=" + resumed);
+        pw.println("\tmConsumer=" + mConsumer.getName());
         ActiveGestureLog.INSTANCE.dump("", pw);
         RecentsModel.INSTANCE.get(this).dump("", pw);
+        if (mTaskAnimationManager != null) {
+            mTaskAnimationManager.dump("", pw);
+        }
         if (createdOverviewActivity != null) {
             createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
         }
         mTaskbarManager.dumpLogs("", pw);
         pw.println("AssistStateManager:");
-        AssistStateManager.INSTANCE.get(this).dump("  ", pw);
+        AssistStateManager.INSTANCE.get(this).dump("\t", pw);
         SystemUiProxy.INSTANCE.get(this).dump(pw);
         DeviceConfigWrapper.get().dump("   ", pw);
     }
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index e3772bd..cfa6b98 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -277,7 +277,8 @@
                     errorDetected |= printErrorIfTrue(
                             true,
                             prefix,
-                            /* errorMessage= */ "new gesture attempted while a requested recents"
+                            /* errorMessage= */ (eventEntry.getDuplicateCount() + 1)
+                                    + " gesture(s) attempted while a requested recents"
                                     + " animation is still pending.",
                             writer);
                     break;
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 1e05a69..c54862a 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -216,6 +216,10 @@
             return gestureEvent;
         }
 
+        public int getDuplicateCount() {
+            return duplicateCount;
+        }
+
         private void update(
                 @NonNull CompoundString compoundString,
                 ActiveGestureErrorDetector.GestureEvent gestureEvent) {