Merge "Fix Taskbar Background Visibility After Entering IME then Overview." into 24D1-dev
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 1ddbaba..a9a8495 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -208,3 +208,13 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+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/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 58c616f..b3ccffc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -419,6 +419,13 @@
     }
 
     @Override
+    protected boolean canToggleHomeAllApps() {
+        return mLauncher.isResumed()
+                && !mTaskbarLauncherStateController.isInOverview()
+                && !mLauncher.areFreeformTasksVisible();
+    }
+
+    @Override
     public RecentsView getRecentsView() {
         return mLauncher.getOverviewPanel();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8bc9ce0..eaee6ce 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1572,4 +1572,8 @@
     public void closeKeyboardQuickSwitchView() {
         mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
     }
+
+    boolean canToggleHomeAllApps() {
+        return mControllers.uiController.canToggleHomeAllApps();
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ff33ca9..ecbc7e7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -23,7 +23,6 @@
 
 import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
-import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
@@ -69,6 +68,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.AllAppsActionManager;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
@@ -158,6 +158,8 @@
     private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
             new SimpleBroadcastReceiver(this::showTaskbarFromBroadcast);
 
+    private final AllAppsActionManager mAllAppsActionManager;
+
     private final Runnable mActivityOnDestroyCallback = new Runnable() {
         @Override
         public void run() {
@@ -212,12 +214,14 @@
     private Boolean mFolded;
 
     @SuppressLint("WrongConstant")
-    public TaskbarManager(TouchInteractionService service) {
+    public TaskbarManager(
+            TouchInteractionService service, AllAppsActionManager allAppsActionManager) {
         Display display =
                 service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
         mContext = service.createWindowContext(display,
                 ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
                 null);
+        mAllAppsActionManager = allAppsActionManager;
         mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
                 ? service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
                 : null;
@@ -291,10 +295,10 @@
                     recreateTaskbar();
                 } else {
                     // Config change might be handled without re-creating the taskbar
-                    if (dp != null && !isTaskbarPresent(dp)) {
+                    if (dp != null && !isTaskbarEnabled(dp)) {
                         destroyExistingTaskbar();
                     } else {
-                        if (dp != null && isTaskbarPresent(dp)) {
+                        if (dp != null && isTaskbarEnabled(dp)) {
                             if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
                                 // Re-initialize for screen size change? Should this be done
                                 // by looking at screen-size change flag in configDiff in the
@@ -349,7 +353,7 @@
         }
         DeviceProfile dp = mUserUnlocked ?
                 LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
-        if (dp == null || !isTaskbarPresent(dp)) {
+        if (dp == null || !isTaskbarEnabled(dp)) {
             removeTaskbarRootViewFromWindow();
         }
     }
@@ -369,20 +373,11 @@
      * @param homeAllAppsIntent Intent used if Taskbar is not enabled or Launcher is resumed.
      */
     public void toggleAllApps(Intent homeAllAppsIntent) {
-        if (mTaskbarActivityContext == null) {
+        if (mTaskbarActivityContext == null || mTaskbarActivityContext.canToggleHomeAllApps()) {
             mContext.startActivity(homeAllAppsIntent);
-            return;
+        } else {
+            mTaskbarActivityContext.toggleAllAppsSearch();
         }
-
-        if (mActivity != null
-                && mActivity.isResumed()
-                && !mActivity.isInState(OVERVIEW)
-                && !(mActivity instanceof QuickstepLauncher l && l.areFreeformTasksVisible())) {
-            mContext.startActivity(homeAllAppsIntent);
-            return;
-        }
-
-        mTaskbarActivityContext.toggleAllAppsSearch();
     }
 
     /**
@@ -477,9 +472,12 @@
             DeviceProfile dp = mUserUnlocked ?
                 LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
 
+            // All Apps action is unrelated to navbar unification, so we only need to check DP.
+            mAllAppsActionManager.setTaskbarPresent(dp != null && dp.isTaskbarPresent);
+
             destroyExistingTaskbar();
 
-            boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
+            boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp);
             debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
                 + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
                 + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
@@ -544,7 +542,7 @@
         }
     }
 
-    private static boolean isTaskbarPresent(DeviceProfile deviceProfile) {
+    private static boolean isTaskbarEnabled(DeviceProfile deviceProfile) {
         return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index c74fd83..cb0fa40 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -197,6 +197,11 @@
         return false;
     }
 
+    /** Returns {@code true} if Home All Apps available instead of Taskbar All Apps. */
+    protected boolean canToggleHomeAllApps() {
+        return false;
+    }
+
     @CallSuper
     protected void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(String.format(
diff --git a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
new file mode 100644
index 0000000..fd2ed3a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS
+import android.app.PendingIntent
+import android.app.RemoteAction
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.view.accessibility.AccessibilityManager
+import com.android.launcher3.R
+import java.util.concurrent.Executor
+
+/**
+ * Registers a [RemoteAction] for toggling All Apps if needed.
+ *
+ * We need this action when either [isHomeAndOverviewSame] or [isTaskbarPresent] is `true`. When
+ * home and overview are the same, we can control Launcher's or Taskbar's All Apps tray. If they are
+ * not the same, but Taskbar is present, we can only control Taskbar's tray.
+ */
+class AllAppsActionManager(
+    private val context: Context,
+    private val bgExecutor: Executor,
+    private val createAllAppsPendingIntent: () -> PendingIntent,
+) {
+
+    /** `true` if home and overview are the same Activity. */
+    var isHomeAndOverviewSame = false
+        set(value) {
+            field = value
+            updateSystemAction()
+        }
+
+    /** `true` if Taskbar is enabled. */
+    var isTaskbarPresent = false
+        set(value) {
+            field = value
+            updateSystemAction()
+        }
+
+    /** `true` if the action should be registered. */
+    var isActionRegistered = false
+        private set
+
+    private fun updateSystemAction() {
+        val shouldRegisterAction = isHomeAndOverviewSame || isTaskbarPresent
+        if (isActionRegistered == shouldRegisterAction) return
+        isActionRegistered = shouldRegisterAction
+
+        bgExecutor.execute {
+            val accessibilityManager =
+                context.getSystemService(AccessibilityManager::class.java) ?: return@execute
+            if (shouldRegisterAction) {
+                accessibilityManager.registerSystemAction(
+                    RemoteAction(
+                        Icon.createWithResource(context, R.drawable.ic_apps),
+                        context.getString(R.string.all_apps_label),
+                        context.getString(R.string.all_apps_label),
+                        createAllAppsPendingIntent(),
+                    ),
+                    GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
+                )
+            } else {
+                accessibilityManager.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS)
+            }
+        }
+    }
+
+    fun onDestroy() {
+        context
+            .getSystemService(AccessibilityManager::class.java)
+            ?.unregisterSystemAction(
+                GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
+            )
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index d02909c..63833c2 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 556dd7e..b10fba4 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -24,6 +24,8 @@
 import android.os.Bundle;
 import android.view.RemoteAnimationTarget;
 
+import java.io.PrintWriter;
+
 /**
  * Extension of {@link RemoteAnimationTargets} with additional information about swipe
  * up animation
@@ -62,4 +64,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 4e62d60..53275a8 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
@@ -102,8 +105,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;
     }
 
     /**
@@ -144,7 +155,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
@@ -185,13 +201,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);
             }
 
@@ -301,13 +329,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);
@@ -437,7 +481,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 719c4f7..8cd733b 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;
@@ -31,6 +32,7 @@
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE;
 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.OnboardingPrefs.HOME_BOUNCE_SEEN;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
@@ -40,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;
@@ -59,14 +62,12 @@
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
 
 import android.app.PendingIntent;
-import android.app.RemoteAction;
 import android.app.Service;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Region;
-import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
@@ -77,7 +78,6 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.MotionEvent;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
@@ -88,7 +88,6 @@
 import com.android.launcher3.ConstantItem;
 import com.android.launcher3.EncryptionType;
 import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.provider.RestoreDbTask;
@@ -101,7 +100,6 @@
 import com.android.launcher3.uioverrides.flags.FlagsFactory;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ScreenOnTracker;
@@ -488,6 +486,7 @@
 
     private TaskbarManager mTaskbarManager;
     private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
+    private AllAppsActionManager mAllAppsActionManager;
 
     @Override
     public void onCreate() {
@@ -497,7 +496,9 @@
         mMainChoreographer = Choreographer.getInstance();
         mAM = ActivityManagerWrapper.getInstance();
         mDeviceState = new RecentsAnimationDeviceState(this, true);
-        mTaskbarManager = new TaskbarManager(this);
+        mAllAppsActionManager = new AllAppsActionManager(
+                this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
+        mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager);
         mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
         BootAwarePreloader.start(this);
@@ -590,16 +591,7 @@
     }
 
     private void onOverviewTargetChange(boolean isHomeAndOverviewSame) {
-        Executors.UI_HELPER_EXECUTOR.execute(() -> {
-            AccessibilityManager am = getSystemService(AccessibilityManager.class);
-
-            if (isHomeAndOverviewSame) {
-                am.registerSystemAction(
-                        createAllAppsAction(), GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
-            } else {
-                am.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
-            }
-        });
+        mAllAppsActionManager.setHomeAndOverviewSame(isHomeAndOverviewSame);
 
         StatefulActivity newOverviewActivity = mOverviewComponentObserver.getActivityInterface()
                 .getCreatedActivity();
@@ -609,13 +601,12 @@
         mTISBinder.onOverviewTargetChange();
     }
 
-    private RemoteAction createAllAppsAction() {
+    private PendingIntent createAllAppsPendingIntent() {
         final Intent homeIntent = new Intent(mOverviewComponentObserver.getHomeIntent())
                 .setAction(INTENT_ACTION_ALL_APPS_TOGGLE);
-        final PendingIntent actionPendingIntent;
 
         if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
-            actionPendingIntent = new PendingIntent(new IIntentSender.Stub() {
+            return new PendingIntent(new IIntentSender.Stub() {
                 @Override
                 public void send(int code, Intent intent, String resolvedType,
                         IBinder allowlistToken, IIntentReceiver finishedReceiver,
@@ -624,18 +615,12 @@
                 }
             });
         } else {
-            actionPendingIntent = PendingIntent.getActivity(
+            return PendingIntent.getActivity(
                     this,
                     GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
                     homeIntent,
                     PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         }
-
-        return new RemoteAction(
-                Icon.createWithResource(this, R.drawable.ic_apps),
-                getString(R.string.all_apps_label),
-                getString(R.string.all_apps_label),
-                actionPendingIntent);
     }
 
     @UiThread
@@ -678,8 +663,7 @@
         mDeviceState.destroy();
         SystemUiProxy.INSTANCE.get(this).clearProxy();
 
-        getSystemService(AccessibilityManager.class)
-                .unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
+        mAllAppsActionManager.onDestroy();
 
         mTaskbarManager.destroy();
         sConnected = false;
@@ -734,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");
 
@@ -1449,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().getCreatedActivity();
         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);
     }
 
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) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 0fc16ba..4fd0f3c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -2759,18 +2759,20 @@
      * Returns true if we should add a stub taskView for the running task id
      */
     protected boolean shouldAddStubTaskView(Task[] runningTasks) {
-        if (runningTasks.length > 1) {
-            TaskView primaryTaskView = getTaskViewByTaskId(runningTasks[0].key.id);
-            TaskView secondaryTaskView = getTaskViewByTaskId(runningTasks[1].key.id);
-            int leftTopTaskViewId =
-                    (primaryTaskView == null) ? -1 : primaryTaskView.getTaskViewId();
-            int rightBottomTaskViewId =
-                    (secondaryTaskView == null) ? -1 : secondaryTaskView.getTaskViewId();
-            // Add a new stub view if both taskIds don't match any taskViews
-            return leftTopTaskViewId != rightBottomTaskViewId || leftTopTaskViewId == -1;
+        TaskView taskView = getTaskViewByTaskId(runningTasks[0].key.id);
+        if (taskView == null) {
+            // No TaskView found, add a stub task.
+            return true;
         }
-        Task runningTaskInfo = runningTasks[0];
-        return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.key.id) == null;
+
+        if (runningTasks.length > 1) {
+            // Ensure all taskIds matches the TaskView, otherwise add a stub task.
+            return Arrays.stream(runningTasks).anyMatch(
+                    runningTask -> !taskView.containsTaskId(runningTask.key.id));
+        } else {
+            // Ensure the TaskView only contains a single taskId, otherwise add a stub task.
+            return taskView.containsMultipleTasks();
+        }
     }
 
     /**
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
new file mode 100644
index 0000000..73b35e8
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.app.PendingIntent
+import android.content.IIntentSender
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.TestUtil
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit.SECONDS
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TIMEOUT = 5L
+
+@RunWith(AndroidJUnit4::class)
+class AllAppsActionManagerTest {
+    private val callbackSemaphore = Semaphore(0)
+    private val bgExecutor = UI_HELPER_EXECUTOR
+
+    private val allAppsActionManager =
+        AllAppsActionManager(
+            InstrumentationRegistry.getInstrumentation().targetContext,
+            bgExecutor,
+        ) {
+            callbackSemaphore.release()
+            PendingIntent(IIntentSender.Default())
+        }
+
+    @Test
+    fun taskbarPresent_actionRegistered() {
+        allAppsActionManager.isTaskbarPresent = true
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+        assertThat(allAppsActionManager.isActionRegistered).isTrue()
+    }
+
+    @Test
+    fun homeAndOverviewSame_actionRegistered() {
+        allAppsActionManager.isHomeAndOverviewSame = true
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+        assertThat(allAppsActionManager.isActionRegistered).isTrue()
+    }
+
+    @Test
+    fun toggleTaskbar_destroyedAfterActionRegistered_actionUnregistered() {
+        allAppsActionManager.isTaskbarPresent = true
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+
+        allAppsActionManager.isTaskbarPresent = false
+        TestUtil.runOnExecutorSync(bgExecutor) {} // Force system action to unregister.
+        assertThat(allAppsActionManager.isActionRegistered).isFalse()
+    }
+
+    @Test
+    fun toggleTaskbar_destroyedBeforeActionRegistered_pendingActionUnregistered() {
+        allAppsActionManager.isTaskbarPresent = true
+        allAppsActionManager.isTaskbarPresent = false
+
+        TestUtil.runOnExecutorSync(bgExecutor) {} // Force system action to unregister.
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+        assertThat(allAppsActionManager.isActionRegistered).isFalse()
+    }
+
+    @Test
+    fun changeHome_sameAsOverviewBeforeActionUnregistered_actionRegisteredAgain() {
+        allAppsActionManager.isHomeAndOverviewSame = true // Initialize to same.
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+
+        allAppsActionManager.isHomeAndOverviewSame = false
+        allAppsActionManager.isHomeAndOverviewSame = true
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+        assertThat(allAppsActionManager.isActionRegistered).isTrue()
+    }
+}