Removing AM.getTasks call on UI thread during touch down

We maintain a cached task queue using TaskStackChangeListener.
It keeps the top RunningTaskInfo partially in sync with platfrom:
   taskInfo is only initialized when the top task changes
   but not when there are changes within the task itself.

Bug: 214581894
Bug: 220742398
Test: Manual and presubmit
Change-Id: Ifafde574040c03435b78f044350c45f3b49c4bcb
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 4d3b057..7bfb76a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -48,8 +48,8 @@
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.AssistantUtilities;
 import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
 
@@ -112,7 +112,8 @@
             return true;
         }
         if (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
-                && AssistantUtilities.isExcludedAssistantRunning()) {
+                && TopTaskTracker.INSTANCE.get(mLauncher).getCachedTopTask(false)
+                        .isExcludedAssistant()) {
             return true;
         }
         return false;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6e80402..2ae0646 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -102,7 +102,6 @@
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.InputConsumerProxy;
 import com.android.quickstep.util.InputProxyHandlerFactory;
-import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.RecentsOrientedState;
@@ -114,6 +113,7 @@
 import com.android.quickstep.util.VibratorWrapper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
@@ -564,24 +564,12 @@
     }
 
     protected void notifyGestureAnimationStartToRecents() {
-        ActivityManager.RunningTaskInfo[] runningTasks;
+        Task[] runningTasks;
         if (mIsSwipeForStagedSplit) {
-            int[] splitTaskIds =
-                    LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
-            runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
-            for (int i = 0; i < splitTaskIds.length; i++) {
-                int taskId = splitTaskIds[i];
-                // Order matters here, we want first indexed RunningTaskInfo to be leftTop task
-                for (ActivityManager.RunningTaskInfo rti : mGestureState.getRunningTasks()) {
-                    if (taskId == rti.taskId) {
-                        runningTasks[i] = rti;
-                        break;
-                    }
-
-                }
-            }
+            int[] splitTaskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
+            runningTasks = mGestureState.getRunningTask().getPlaceholderTasks(splitTaskIds);
         } else {
-            runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()};
+            runningTasks = mGestureState.getRunningTask().getPlaceholderTasks();
         }
         mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
     }
@@ -801,7 +789,7 @@
             RecentsAnimationTargets targets) {
         super.onRecentsAnimationStart(controller, targets);
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
-        mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
+        mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets);
         mRecentsAnimationController = controller;
         mRecentsAnimationTargets = targets;
 
@@ -1380,7 +1368,7 @@
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
-        final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
+        final ActivityManager.RunningTaskInfo taskInfo = runningTaskTarget.taskInfo;
         final RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
                 .getOrientationState();
         final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
@@ -1782,8 +1770,7 @@
                     new PictureInPictureSurfaceTransaction.Builder()
                             .setAlpha(0f)
                             .build();
-            int[] taskIds =
-                        LauncherSplitScreenListener.INSTANCE.getNoCreate().getRunningSplitTaskIds();
+            int[] taskIds = TopTaskTracker.INSTANCE.get(mContext).getRunningSplitTaskIds();
             for (int taskId : taskIds) {
                 mRecentsAnimationController.setFinishTaskTransaction(taskId,
                         tx, null /* overlay */);
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 9828467..ee5bb44 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -30,7 +30,7 @@
 
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
@@ -69,7 +69,6 @@
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.TransformParams.BuilderProxy;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
@@ -109,7 +108,7 @@
         super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
                 continuingLastGesture, inputConsumer);
 
-        mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
+        mRunningOverHome = mGestureState.getRunningTask().isHomeTask();
         if (mRunningOverHome) {
             runActionOnRemoteHandles(remoteTargetHandle ->
                     remoteTargetHandle.getTransformParams().setHomeBuilderProxy(
@@ -150,16 +149,17 @@
             return new FallbackPipToHomeAnimationFactory();
         }
         mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
-        startHomeIntent(mActiveAnimationFactory);
+        startHomeIntent(mActiveAnimationFactory, runningTaskTarget);
         return mActiveAnimationFactory;
     }
 
     private void startHomeIntent(
-            @Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory) {
+            @Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory,
+            @Nullable RemoteAnimationTargetCompat runningTaskTarget) {
         ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
         Intent intent = new Intent(mGestureState.getHomeIntent());
-        if (gestureContractAnimationFactory != null) {
-            gestureContractAnimationFactory.addGestureContract(intent);
+        if (gestureContractAnimationFactory != null && runningTaskTarget != null) {
+            gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
         }
         try {
             mContext.startActivity(intent, options.toBundle());
@@ -187,7 +187,8 @@
             // the PiP task appearing.
             recentsCallback = () -> {
                 callback.run();
-                startHomeIntent(null /* gestureContractAnimationFactory */);
+                startHomeIntent(
+                        null /* gestureContractAnimationFactory */, null /* runningTaskTarget */);
             };
         } else {
             recentsCallback = callback;
@@ -212,7 +213,7 @@
         if (mRunningOverHome) {
             if (DisplayController.getNavigationMode(mContext).hasGestures) {
                 mRecentsView.onGestureAnimationStartOnHome(
-                        new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()},
+                        mGestureState.getRunningTask().getPlaceholderTasks(),
                         mDeviceState.getRotationTouchHelper());
             }
         } else {
@@ -396,12 +397,12 @@
             }
         }
 
-        private void addGestureContract(Intent intent) {
-            if (mRunningOverHome || mGestureState.getRunningTask() == null) {
+        private void addGestureContract(Intent intent, RunningTaskInfo runningTaskInfo) {
+            if (mRunningOverHome || runningTaskInfo == null) {
                 return;
             }
 
-            TaskKey key = new TaskKey(mGestureState.getRunningTask());
+            TaskKey key = new TaskKey(runningTaskInfo);
             if (key.getComponent() != null) {
                 if (sMessageReceiver == null) {
                     sMessageReceiver = new StaticMessageReceiver();
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index ed0623d..3b52e91 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -22,7 +22,6 @@
 
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
-import android.app.ActivityManager;
 import android.content.Intent;
 import android.os.Build;
 
@@ -30,6 +29,7 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.tracing.GestureStateProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
+import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -135,8 +135,7 @@
     private final MultiStateCallback mStateCallback;
     private final int mGestureId;
 
-    private ActivityManager.RunningTaskInfo mRunningTask;
-    private ActivityManager.RunningTaskInfo[] mRunningTasks;
+    private CachedTaskInfo mRunningTask;
     private GestureEndTarget mEndTarget;
     private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
     private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
@@ -232,42 +231,25 @@
     /**
      * @return the running task for this gesture.
      */
-    public ActivityManager.RunningTaskInfo getRunningTask() {
+    public CachedTaskInfo getRunningTask() {
         return mRunningTask;
     }
 
     /**
-     * This will array will contain the task returned by {@link #getRunningTask()}
-     * @return the running tasks for this gesture.
-     */
-    public ActivityManager.RunningTaskInfo[] getRunningTasks() {
-        return mRunningTasks;
-    }
-
-    /**
      * @return the running task id for this gesture.
      */
     public int getRunningTaskId() {
-        return mRunningTask != null ? mRunningTask.taskId : -1;
+        return mRunningTask != null ? mRunningTask.getTaskId() : -1;
     }
 
     /**
      * Updates the running task for the gesture to be the given {@param runningTask}.
      */
-    public void updateRunningTask(ActivityManager.RunningTaskInfo runningTask) {
+    public void updateRunningTask(CachedTaskInfo runningTask) {
         mRunningTask = runningTask;
     }
 
     /**
-     * TODO(b/210903248) refactor to consolidate w/ method above
-     * Updates the running task for the gesture to be the given {@param runningTask}.
-     */
-    public void updateRunningTasks(ActivityManager.RunningTaskInfo[] runningTasks) {
-        mRunningTasks = runningTasks;
-        updateRunningTask(runningTasks[0]);
-    }
-
-    /**
      * Updates the last task that appeared during this gesture.
      */
     public void updateLastAppearedTaskTarget(RemoteAnimationTargetCompat lastAppearedTaskTarget) {
@@ -339,7 +321,7 @@
      * user controlled gesture.
      */
     public void setHandlingAtomicEvent(boolean handlingAtomicEvent) {
-        mHandlingAtomicEvent = true;
+        mHandlingAtomicEvent = handlingAtomicEvent;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 7ad60bb..920ed71 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -45,14 +45,11 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 
-import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.graphics.Region;
 import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
@@ -61,18 +58,17 @@
 import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.text.TextUtils;
 import android.view.MotionEvent;
 
 import androidx.annotation.BinderThread;
 
-import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.DisplayController.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
+import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -83,7 +79,6 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Manages the state of the system during a swipe up gesture.
@@ -97,7 +92,6 @@
     private final int mDisplayId;
     private final RotationTouchHelper mRotationTouchHelper;
     private final TaskStackChangeListener mPipListener;
-    private final List<ComponentName> mGestureBlockedActivities;
     // Cache for better performance since it doesn't change at runtime.
     private final boolean mCanImeRenderGesturalNavButtons =
             InputMethodService.canImeRenderGesturalNavButtons();
@@ -129,6 +123,7 @@
         }
     };
 
+    private int mGestureBlockingTaskId = -1;
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
@@ -178,22 +173,6 @@
         onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL);
         runOnDestroy(() -> mDisplayController.removeChangeListener(this));
 
-        // Add any blocked activities
-        String[] blockingActivities;
-        try {
-            blockingActivities =
-                    context.getResources().getStringArray(R.array.gesture_blocking_activities);
-        } catch (Resources.NotFoundException e) {
-            blockingActivities = new String[0];
-        }
-        mGestureBlockedActivities = new ArrayList<>(blockingActivities.length);
-        for (String blockingActivity : blockingActivities) {
-            if (!TextUtils.isEmpty(blockingActivity)) {
-                mGestureBlockedActivities.add(
-                        ComponentName.unflattenFromString(blockingActivity));
-            }
-        }
-
         SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
         if (mIsOneHandedModeSupported) {
             Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
@@ -367,11 +346,17 @@
     }
 
     /**
-     * @return whether the given running task info matches the gesture-blocked activity.
+     * Sets the task id where gestures should be blocked
      */
-    public boolean isGestureBlockedActivity(ActivityManager.RunningTaskInfo runningTaskInfo) {
-        return runningTaskInfo != null
-                && mGestureBlockedActivities.contains(runningTaskInfo.topActivity);
+    public void setGestureBlockingTaskId(int taskId) {
+        mGestureBlockingTaskId = taskId;
+    }
+
+    /**
+     * @return whether the given running task info matches the gesture-blocked task.
+     */
+    public boolean isGestureBlockedTask(CachedTaskInfo taskInfo) {
+        return taskInfo != null && taskInfo.getTaskId() == mGestureBlockingTaskId;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index afb4d4d..c3ea256 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -22,7 +22,6 @@
 
 import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -50,8 +49,7 @@
      * running tasks
      */
     public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
-        int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
-                .getRunningSplitTaskIds();
+        int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
         mRemoteTargetHandles = createHandles(context, sizingStrategy, splitIds.length == 2 ? 2 : 1);
     }
 
@@ -73,7 +71,7 @@
      * Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
      *
      * If split screen may be active when this is called, you might want to use
-     * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
+     * {@link #assignTargetsForSplitScreen(Context, RemoteAnimationTargets)}
      */
     public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
         for (int i = 0; i < mRemoteTargetHandles.length; i++) {
@@ -90,9 +88,9 @@
      * apps in targets.apps to that of the _active_ split screened tasks.
      * See {@link #assignTargetsForSplitScreen(RemoteAnimationTargets, int[])}
      */
-    public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
-        int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
-                .getRunningSplitTaskIds();
+    public RemoteTargetHandle[] assignTargetsForSplitScreen(
+            Context context, RemoteAnimationTargets targets) {
+        int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
         return assignTargetsForSplitScreen(targets, splitIds);
     }
 
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 8862073..088e1cf 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -37,7 +37,6 @@
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
-import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -82,8 +81,7 @@
         mGestureState = gestureState;
 
         mIsSwipeForStagedSplit = ENABLE_SPLIT_SELECT.get() &&
-                LauncherSplitScreenListener.INSTANCE.getNoCreate()
-                        .getRunningSplitTaskIds().length > 1;
+                TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds().length > 1;
 
         mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getActivityInterface());
         mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 4bb2400..b8334a9 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -35,6 +35,7 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -234,9 +235,8 @@
             // Allowing to pause Home if Home is top activity and Recents is not Home. So when user
             // start home when recents animation is playing, the home activity can be resumed again
             // to let the transition controller collect Home activity.
-            ActivityManager.RunningTaskInfo rti = gestureState.getRunningTask();
-            boolean homeIsOnTop = rti != null && rti.topActivity != null
-                    && rti.topActivity.equals(gestureState.getHomeIntent().getComponent());
+            CachedTaskInfo cti = gestureState.getRunningTask();
+            boolean homeIsOnTop = cti != null && cti.isHomeTask();
             if (!homeIsOnTop) {
                 options.setTransientLaunch();
             }
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
new file mode 100644
index 0000000..80bc329
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2022 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 static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.content.Intent.ACTION_CHOOSER;
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+
+import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.launcher3.util.SplitConfigurationOptions.StageType;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
+import com.android.launcher3.util.TraceHelper;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.splitscreen.ISplitScreenListener;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This class tracked the top-most task and  some 'approximate' task history to allow faster
+ * system state estimation during touch interaction
+ */
+public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskStackChangeListener {
+
+    public static MainThreadInitializedObject<TopTaskTracker> INSTANCE =
+            new MainThreadInitializedObject<>(TopTaskTracker::new);
+
+    private static final int HISTORY_SIZE = 5;
+
+    // Ordered list with first item being the most recent task.
+    private final LinkedList<RunningTaskInfo> mOrderedTaskList = new LinkedList<>();
+
+    private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
+    private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
+
+    private TopTaskTracker(Context context) {
+        mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
+        mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
+
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
+        SystemUiProxy.INSTANCE.get(context).registerSplitScreenListener(this);
+    }
+
+    @Override
+    public void onTaskRemoved(int taskId) {
+        mOrderedTaskList.removeIf(rto -> rto.taskId == taskId);
+    }
+
+    @Override
+    public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
+        mOrderedTaskList.removeIf(rto -> rto.taskId == taskInfo.taskId);
+        mOrderedTaskList.addFirst(taskInfo);
+        if (mOrderedTaskList.size() >= HISTORY_SIZE) {
+            // If we grow in size, remove the last taskInfo which is not part of the split task.
+            Iterator<RunningTaskInfo> itr = mOrderedTaskList.descendingIterator();
+            while (itr.hasNext()) {
+                RunningTaskInfo info = itr.next();
+                if (info.taskId != taskInfo.taskId
+                        && info.taskId != mMainStagePosition.taskId
+                        && info.taskId != mSideStagePosition.taskId) {
+                    itr.remove();
+                    return;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
+        if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
+            mMainStagePosition.stagePosition = position;
+        } else {
+            mSideStagePosition.stagePosition = position;
+        }
+    }
+
+    @Override
+    public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
+        // If task is not visible but we are tracking it, stop tracking it
+        if (!visible) {
+            if (mMainStagePosition.taskId == taskId) {
+                resetTaskId(mMainStagePosition);
+            } else if (mSideStagePosition.taskId == taskId) {
+                resetTaskId(mSideStagePosition);
+            } // else it's an un-tracked child
+            return;
+        }
+
+        // If stage has moved to undefined, stop tracking the task
+        if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
+            resetTaskId(taskId == mMainStagePosition.taskId
+                    ? mMainStagePosition : mSideStagePosition);
+            return;
+        }
+
+        if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
+            mMainStagePosition.taskId = taskId;
+        } else {
+            mSideStagePosition.taskId = taskId;
+        }
+    }
+
+    private void resetTaskId(StagedSplitTaskPosition taskPosition) {
+        taskPosition.taskId = -1;
+    }
+
+    /**
+     * @return index 0 will be task in left/top position, index 1 in right/bottom position.
+     *         Will return empty array if device is not in staged split
+     */
+    public int[] getRunningSplitTaskIds() {
+        if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) {
+            return new int[]{};
+        }
+        int[] out = new int[2];
+        if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
+            out[0] = mMainStagePosition.taskId;
+            out[1] = mSideStagePosition.taskId;
+        } else {
+            out[1] = mMainStagePosition.taskId;
+            out[0] = mSideStagePosition.taskId;
+        }
+        return out;
+    }
+
+
+    /**
+     * Returns the CachedTaskInfo for the top most task
+     */
+    @UiThread
+    public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
+        if (filterOnlyVisibleRecents) {
+            // Since we only know about the top most task, any filtering may not be applied on the
+            // cache. The second to top task may change while the top task is still the same.
+            RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
+                    ActivityManagerWrapper.getInstance().getRunningTasks(true));
+            return new CachedTaskInfo(Arrays.asList(tasks));
+        }
+
+        if (mOrderedTaskList.isEmpty()) {
+            RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.false", () ->
+                    ActivityManagerWrapper.getInstance().getRunningTasks(
+                            false /* filterOnlyVisibleRecents */));
+            Collections.addAll(mOrderedTaskList, tasks);
+        }
+        return new CachedTaskInfo(new ArrayList<>(mOrderedTaskList));
+    }
+
+    /**
+     * Class to provide information about a task which can be safely cached and do not change
+     * during the lifecycle of the task.
+     */
+    public static class CachedTaskInfo {
+
+        private final RunningTaskInfo mTopTask;
+        private final List<RunningTaskInfo> mAllCachedTasks;
+
+        CachedTaskInfo(List<RunningTaskInfo> allCachedTasks) {
+            mAllCachedTasks = allCachedTasks;
+            mTopTask = allCachedTasks.get(0);
+        }
+
+        public int getTaskId() {
+            return mTopTask.taskId;
+        }
+
+        /**
+         * Returns true if the root of the task chooser activity
+         */
+        public boolean isRootChooseActivity() {
+            return ACTION_CHOOSER.equals(mTopTask.baseIntent.getAction());
+        }
+
+        /**
+         * Returns true if the given task holds an Assistant activity that is excluded from recents
+         */
+        public boolean isExcludedAssistant() {
+            return mTopTask.configuration.windowConfiguration
+                    .getActivityType() == ACTIVITY_TYPE_ASSISTANT
+                    && (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+        }
+
+        /**
+         * Returns true if this represents the HOME task
+         */
+        public boolean isHomeTask() {
+            return mTopTask.configuration.windowConfiguration
+                    .getActivityType() == ACTIVITY_TYPE_HOME;
+        }
+
+        /**
+         * Returns {@link Task} array which can be used as a placeholder until the true object
+         * is loaded by the model
+         */
+        public Task[] getPlaceholderTasks() {
+            return new Task[] {Task.from(new TaskKey(mTopTask), mTopTask, false)};
+        }
+
+        /**
+         * Returns {@link Task} array corresponding to the provided task ids which can be used as a
+         * placeholder until the true object is loaded by the model
+         */
+        public Task[] getPlaceholderTasks(int[] taskIds) {
+            Task[] result = new Task[taskIds.length];
+            for (int i = 0; i < taskIds.length; i++) {
+                final int index = i;
+                int taskId = taskIds[i];
+                mAllCachedTasks.forEach(rti -> {
+                    if (rti.taskId == taskId) {
+                        result[index] = Task.from(new TaskKey(rti), rti, false);
+                    }
+                });
+            }
+            return result;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 3206825..0078d55 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static android.content.Intent.ACTION_CHOOSER;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
@@ -41,7 +40,6 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.app.Service;
-import android.content.ComponentName;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
@@ -95,8 +93,6 @@
 import com.android.quickstep.inputconsumers.SysUiOverlayInputConsumer;
 import com.android.quickstep.inputconsumers.TaskbarStashInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.AssistantUtilities;
-import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.ProxyScreenStatusProvider;
 import com.android.quickstep.util.SplitScreenBounds;
@@ -314,6 +310,13 @@
         public void setSwipeUpProxy(Function<GestureState, AnimatedFloat> proxy) {
             mSwipeUpProxyProvider = proxy != null ? proxy : (i -> null);
         }
+
+        /**
+         * Sets the task id where gestures should be blocked
+         */
+        public void setGestureBlockedTaskId(int taskId) {
+            mDeviceState.setGestureBlockingTaskId(taskId);
+        }
     }
 
     private static boolean sConnected = false;
@@ -369,7 +372,6 @@
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
 
         ProtoTracer.INSTANCE.get(this).add(this);
-        LauncherSplitScreenListener.INSTANCE.get(this).init();
         sConnected = true;
     }
 
@@ -419,6 +421,9 @@
         onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
         onAssistantVisibilityChanged();
 
+        // Initialize the task tracker
+        TopTaskTracker.INSTANCE.get(this);
+
         // Temporarily disable model preload
         // new ModelPreload().start(this);
         mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this)
@@ -531,7 +536,6 @@
         getSystemService(AccessibilityManager.class)
                 .unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
 
-        LauncherSplitScreenListener.INSTANCE.get(this).destroy();
         mTaskbarManager.destroy();
         sConnected = false;
         super.onDestroy();
@@ -633,7 +637,7 @@
 
     private InputConsumer tryCreateAssistantInputConsumer(InputConsumer base,
             GestureState gestureState, MotionEvent motionEvent) {
-        return mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())
+        return mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())
                 ? base
                 : new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat,
                         mDeviceState, motionEvent);
@@ -648,8 +652,8 @@
             gestureState.updatePreviouslyAppearedTaskIds(
                     previousGestureState.getPreviouslyAppearedTaskIds());
         } else {
-            gestureState.updateRunningTasks(TraceHelper.allowIpcs("getRunningTask.0",
-                    () -> mAM.getRunningTasks(false /* filterOnlyVisibleRecents */)));
+            gestureState.updateRunningTask(
+                    TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false));
         }
         return gestureState;
     }
@@ -743,17 +747,14 @@
         // Use overview input consumer for sharesheets on top of home.
         boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
                 && gestureState.getRunningTask() != null
-                && ACTION_CHOOSER.equals(gestureState.getRunningTask().baseIntent.getAction());
-        if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) {
+                && gestureState.getRunningTask().isRootChooseActivity();
+        if (gestureState.getRunningTask() != null
+                && gestureState.getRunningTask().isExcludedAssistant()) {
             // In the case where we are in the excluded assistant state, ignore it and treat the
             // running activity as the task behind the assistant
-            gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.assistant",
-                    () -> mAM.getRunningTask(true /* filterOnlyVisibleRecents */)));
-            ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
-            ComponentName runningComponent =
-                    gestureState.getRunningTask().baseIntent.getComponent();
-            forceOverviewInputConsumer =
-                    runningComponent != null && runningComponent.equals(homeComponent);
+            gestureState.updateRunningTask(TopTaskTracker.INSTANCE.get(this)
+                    .getCachedTopTask(true /* filterOnlyVisibleRecents */));
+            forceOverviewInputConsumer = gestureState.getRunningTask().isHomeTask();
         }
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()
@@ -770,7 +771,7 @@
                 || forceOverviewInputConsumer) {
             return createOverviewInputConsumer(
                     previousGestureState, gestureState, event, forceOverviewInputConsumer);
-        } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
+        } else if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
             return getDefaultInputConsumer();
         } else {
             return createOtherActivityInputConsumer(gestureState, event);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 48af802..c9ee2db 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -24,7 +24,6 @@
 
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.os.Build;
 import android.util.AttributeSet;
@@ -50,7 +49,6 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
 
 import java.util.ArrayList;
 
@@ -58,7 +56,7 @@
 public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
         implements StateListener<RecentsState> {
 
-    private RunningTaskInfo mHomeTaskInfo;
+    private Task mHomeTask;
 
     public FallbackRecentsView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -87,12 +85,12 @@
      * to the home task. This allows us to handle quick-switch similarly to a quick-switching
      * from a foreground task.
      */
-    public void onGestureAnimationStartOnHome(RunningTaskInfo[] homeTaskInfo,
+    public void onGestureAnimationStartOnHome(Task[] homeTask,
             RotationTouchHelper rotationTouchHelper) {
         // TODO(b/195607777) General fallback love, but this might be correct
         //  Home task should be defined as the front-most task info I think?
-        mHomeTaskInfo = homeTaskInfo[0];
-        onGestureAnimationStart(homeTaskInfo, rotationTouchHelper);
+        mHomeTask = homeTask[0];
+        onGestureAnimationStart(homeTask, rotationTouchHelper);
     }
 
     /**
@@ -105,8 +103,8 @@
             @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
             TaskViewSimulator[] taskViewSimulators) {
         super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulators);
-        if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
-            TaskView tv = getTaskViewByTaskId(mHomeTaskInfo.taskId);
+        if (mHomeTask != null && endTarget == RECENTS && animatorSet != null) {
+            TaskView tv = getTaskViewByTaskId(mHomeTask.key.id);
             if (tv != null) {
                 PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
                         false /* dismissingForSplitSelection*/);
@@ -131,8 +129,8 @@
     public void setCurrentTask(int runningTaskViewId) {
         super.setCurrentTask(runningTaskViewId);
         int runningTaskId = getTaskIdsForRunningTaskView()[0];
-        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId != runningTaskId) {
-            mHomeTaskInfo = null;
+        if (mHomeTask != null && mHomeTask.key.id != runningTaskId) {
+            mHomeTask = null;
             setRunningTaskHidden(false);
         }
     }
@@ -140,26 +138,26 @@
     @Nullable
     @Override
     protected TaskView getHomeTaskView() {
-        return mHomeTaskInfo != null ? getTaskViewByTaskId(mHomeTaskInfo.taskId) : null;
+        return mHomeTask != null ? getTaskViewByTaskId(mHomeTask.key.id) : null;
     }
 
     @Override
-    protected boolean shouldAddStubTaskView(RunningTaskInfo[] runningTaskInfos) {
-        if (runningTaskInfos.length > 1) {
+    protected boolean shouldAddStubTaskView(Task[] runningTasks) {
+        if (runningTasks.length > 1) {
             // can't be in split screen w/ home task
-            return super.shouldAddStubTaskView(runningTaskInfos);
+            return super.shouldAddStubTaskView(runningTasks);
         }
 
-        RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
-        if (mHomeTaskInfo != null && runningTaskInfo != null &&
-                mHomeTaskInfo.taskId == runningTaskInfo.taskId
+        Task runningTask = runningTasks[0];
+        if (mHomeTask != null && runningTask != null
+                && mHomeTask.key.id == runningTask.key.id
                 && getTaskViewCount() == 0 && mLoadPlanEverApplied) {
             // Do not add a stub task if we are running over home with empty recents, so that we
             // show the empty recents message instead of showing a stub task and later removing it.
             // Ignore empty task signal if applyLoadPlan has never run.
             return false;
         }
-        return super.shouldAddStubTaskView(runningTaskInfos);
+        return super.shouldAddStubTaskView(runningTasks);
     }
 
     @Override
@@ -169,7 +167,7 @@
         // track the index of the next task appropriately, as if we are switching on any other app.
         // TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
         int runningTaskId = getTaskIdsForRunningTaskView()[0];
-        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId 
+        if (mHomeTask != null && mHomeTask.key.id == runningTaskId
                 && !taskGroups.isEmpty()) {
             // Check if the task list has running task
             boolean found = false;
@@ -182,9 +180,7 @@
             if (!found) {
                 ArrayList<GroupTask> newList = new ArrayList<>(taskGroups.size() + 1);
                 newList.addAll(taskGroups);
-                newList.add(new GroupTask(
-                        Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false),
-                        null, null));
+                newList.add(new GroupTask(mHomeTask, null, null));
                 taskGroups = newList;
             }
         }
@@ -193,7 +189,7 @@
 
     @Override
     public void setRunningTaskHidden(boolean isHidden) {
-        if (mHomeTaskInfo != null) {
+        if (mHomeTask != null) {
             // Always keep the home task hidden
             isHidden = true;
         }
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 5ef9a9b..db19c45 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -21,7 +21,6 @@
 
 import android.animation.Animator;
 import android.app.Activity;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -224,8 +223,7 @@
         if (!state.getHomeIntent().getComponent().getPackageName().equals(getPackageName())) {
             return null;
         }
-        RunningTaskInfo rti = state.getRunningTask();
-        if (rti == null || !rti.topActivity.equals(getComponentName())) {
+        if (state.getRunningTaskId() != getTaskId()) {
             return null;
         }
         mSwipeProgress.updateValue(0);
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 7daac81..bf7023c 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -31,9 +31,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.quickstep.TouchInteractionService.TISBinder;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
+import com.android.quickstep.util.TISBindHelper;
 
-import java.util.List;
+import java.util.Arrays;
 
 /** Shows the gesture interactive sandbox in full screen mode. */
 public class GestureSandboxActivity extends FragmentActivity {
@@ -52,6 +54,9 @@
     private SharedPreferences mSharedPrefs;
     private StatsLogManager mStatsLogManager;
 
+    private TISBindHelper mTISBindHelper;
+    private TISBinder mBinder;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -69,6 +74,8 @@
         getSupportFragmentManager().beginTransaction()
                 .add(R.id.gesture_tutorial_fragment_container, mFragment)
                 .commit();
+
+        mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
     }
 
     @Override
@@ -206,7 +213,37 @@
             DisplayMetrics metrics = new DisplayMetrics();
             display.getMetrics(metrics);
             getWindow().setSystemGestureExclusionRects(
-                    List.of(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
+                    Arrays.asList(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
         }
     }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        updateServiceState(true);
+    }
+
+    private void onTISConnected(TISBinder binder) {
+        mBinder = binder;
+        updateServiceState(isResumed());
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        updateServiceState(false);
+    }
+
+    private void updateServiceState(boolean isEnabled) {
+        if (mBinder != null) {
+            mBinder.setGestureBlockedTaskId(isEnabled ? getTaskId() : -1);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mTISBindHelper.onDestroy();
+        updateServiceState(false);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
deleted file mode 100644
index 336f7d1..0000000
--- a/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2019 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.util;
-
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_ASSISTANT;
-
-import android.annotation.TargetApi;
-import android.app.TaskInfo;
-import android.content.Intent;
-import android.os.Build;
-
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-/**
- * Utility class for interacting with the Assistant.
- */
-@TargetApi(Build.VERSION_CODES.Q)
-public final class AssistantUtilities {
-
-    /** Returns true if an Assistant activity that is excluded from recents is running. */
-    public static boolean isExcludedAssistantRunning() {
-        return isExcludedAssistant(ActivityManagerWrapper.getInstance().getRunningTask());
-    }
-
-    /** Returns true if the given task holds an Assistant activity that is excluded from recents. */
-    public static boolean isExcludedAssistant(TaskInfo info) {
-        return info != null
-            && info.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_ASSISTANT
-            && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
-    }
-
-    private AssistantUtilities() {}
-}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
deleted file mode 100644
index affe625..0000000
--- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package com.android.quickstep.util;
-
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-
-import android.content.Context;
-import android.os.IBinder;
-
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.launcher3.util.SplitConfigurationOptions.StageType;
-import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitTaskPosition;
-import com.android.quickstep.SystemUiProxy;
-import com.android.wm.shell.splitscreen.ISplitScreenListener;
-
-/**
- * Listeners for system wide split screen position and stage changes.
- *
- * Use {@link #getRunningSplitTaskIds()} to determine which tasks, if any, are actively in
- * staged split.
- */
-public class LauncherSplitScreenListener extends ISplitScreenListener.Stub {
-
-    public static final MainThreadInitializedObject<LauncherSplitScreenListener> INSTANCE =
-            new MainThreadInitializedObject<>(LauncherSplitScreenListener::new);
-
-    private static final int[] EMPTY_ARRAY = {};
-
-    private final StagedSplitTaskPosition mMainStagePosition = new StagedSplitTaskPosition();
-    private final StagedSplitTaskPosition mSideStagePosition = new StagedSplitTaskPosition();
-
-    /**
-     * Gets set to current split taskIDs whenever the task list is frozen, and set to empty array
-     * whenever task list unfreezes. This also gets set to empty array whenever the user swipes to
-     * home - in that case the task list does not unfreeze immediately after the gesture, so it's
-     * done via {@link #notifySwipingToHome()}.
-     *
-     * When not empty, this indicates that we need to load a GroupedTaskView as the most recent
-     * page, so user can quickswitch back to a grouped task.
-     */
-    private int[] mPersistentGroupedIds;
-
-    public LauncherSplitScreenListener(Context context) {
-        mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
-        mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
-    }
-
-    /** Also call {@link #destroy()} when done. */
-    public void init() {
-        SystemUiProxy.INSTANCE.getNoCreate().registerSplitScreenListener(this);
-    }
-
-    public void destroy() {
-        SystemUiProxy.INSTANCE.getNoCreate().unregisterSplitScreenListener(this);
-    }
-
-    /**
-     * @return index 0 will be task in left/top position, index 1 in right/bottom position.
-     *         Will return empty array if device is not in staged split
-     */
-    public int[] getRunningSplitTaskIds() {
-        if (mMainStagePosition.taskId == -1 || mSideStagePosition.taskId == -1) {
-            return new int[]{};
-        }
-        int[] out = new int[2];
-        if (mMainStagePosition.stagePosition == STAGE_POSITION_TOP_OR_LEFT) {
-            out[0] = mMainStagePosition.taskId;
-            out[1] = mSideStagePosition.taskId;
-        } else {
-            out[1] = mMainStagePosition.taskId;
-            out[0] = mSideStagePosition.taskId;
-        }
-        return out;
-    }
-
-    @Override
-    public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
-        if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
-            mMainStagePosition.stagePosition = position;
-        } else {
-            mSideStagePosition.stagePosition = position;
-        }
-    }
-
-    @Override
-    public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
-        // If task is not visible but we are tracking it, stop tracking it
-        if (!visible) {
-            if (mMainStagePosition.taskId == taskId) {
-                resetTaskId(mMainStagePosition);
-            } else if (mSideStagePosition.taskId == taskId) {
-                resetTaskId(mSideStagePosition);
-            } // else it's an un-tracked child
-            return;
-        }
-
-        // If stage has moved to undefined, stop tracking the task
-        if (stage == SplitConfigurationOptions.STAGE_TYPE_UNDEFINED) {
-            resetTaskId(taskId == mMainStagePosition.taskId ?
-                    mMainStagePosition : mSideStagePosition);
-            return;
-        }
-
-        if (stage == SplitConfigurationOptions.STAGE_TYPE_MAIN) {
-            mMainStagePosition.taskId = taskId;
-        } else {
-            mSideStagePosition.taskId = taskId;
-        }
-    }
-
-    private void resetTaskId(StagedSplitTaskPosition taskPosition) {
-        taskPosition.taskId = -1;
-    }
-
-    @Override
-    public IBinder asBinder() {
-        return this;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ed00b8e..10da83f 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -64,8 +64,8 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.LocusId;
 import android.content.res.Configuration;
@@ -171,7 +171,6 @@
 import com.android.quickstep.util.VibratorWrapper;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -2080,8 +2079,8 @@
     /**
      * Called when a gesture from an app is starting.
      */
-    public void onGestureAnimationStart(RunningTaskInfo[] runningTaskInfo,
-            RotationTouchHelper rotationTouchHelper) {
+    public void onGestureAnimationStart(
+            Task[] runningTasks, RotationTouchHelper rotationTouchHelper) {
         mGestureActive = true;
         // This needs to be called before the other states are set since it can create the task view
         if (mOrientationState.setGestureActive(true)) {
@@ -2092,7 +2091,7 @@
             updateSizeAndPadding();
         }
 
-        showCurrentTask(runningTaskInfo);
+        showCurrentTask(runningTasks);
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
@@ -2208,10 +2207,10 @@
     /**
      * Returns true if we should add a stub taskView for the running task id
      */
-    protected boolean shouldAddStubTaskView(RunningTaskInfo[] runningTaskInfos) {
-        if (runningTaskInfos.length > 1) {
-            TaskView primaryTaskView = getTaskViewByTaskId(runningTaskInfos[0].taskId);
-            TaskView secondaryTaskView = getTaskViewByTaskId(runningTaskInfos[1].taskId);
+    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 =
@@ -2219,8 +2218,8 @@
             // Add a new stub view if both taskIds don't match any taskViews
             return leftTopTaskViewId != rightBottomTaskViewId || leftTopTaskViewId == -1;
         }
-        RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
-        return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.taskId) == null;
+        Task runningTaskInfo = runningTasks[0];
+        return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.key.id) == null;
     }
 
     /**
@@ -2229,21 +2228,16 @@
      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
      * is called.  Also scrolls the view to this task.
      */
-    private void showCurrentTask(RunningTaskInfo[] runningTaskInfo) {
+    private void showCurrentTask(Task[] runningTasks) {
         int runningTaskViewId = -1;
-        boolean needGroupTaskView = runningTaskInfo.length > 1;
-        RunningTaskInfo taskInfo = runningTaskInfo[0];
-        if (shouldAddStubTaskView(runningTaskInfo)) {
+        boolean needGroupTaskView = runningTasks.length > 1;
+        if (shouldAddStubTaskView(runningTasks)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView;
             if (needGroupTaskView) {
                 taskView = getTaskViewFromPool(true);
-                RunningTaskInfo secondaryTaskInfo = runningTaskInfo[1];
-                mTmpRunningTasks = new Task[]{
-                        Task.from(new TaskKey(taskInfo), taskInfo, false),
-                        Task.from(new TaskKey(secondaryTaskInfo), secondaryTaskInfo, false)
-                };
+                mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
                 addView(taskView, 0);
                 // When we create a placeholder task view mSplitBoundsConfig will be null, but with
                 // the actual app running we won't need to show the thumbnail until all the tasks
@@ -2255,7 +2249,7 @@
                 addView(taskView, 0);
                 // The temporary running task is only used for the duration between the start of the
                 // gesture and the task list is loaded and applied
-                mTmpRunningTasks = new Task[]{Task.from(new TaskKey(taskInfo), taskInfo, false)};
+                mTmpRunningTasks = new Task[]{runningTasks[0]};
                 taskView.bind(mTmpRunningTasks[0], mOrientationState);
             }
             runningTaskViewId = taskView.getTaskViewId();
@@ -2268,8 +2262,8 @@
             measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
                     makeMeasureSpec(getMeasuredHeight(), EXACTLY));
             layout(getLeft(), getTop(), getRight(), getBottom());
-        } else if (getTaskViewByTaskId(taskInfo.taskId) != null) {
-            runningTaskViewId = getTaskViewByTaskId(taskInfo.taskId).getTaskViewId();
+        } else if (getTaskViewByTaskId(runningTasks[0].key.id) != null) {
+            runningTaskViewId = getTaskViewByTaskId(runningTasks[0].key.id).getTaskViewId();
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -4059,6 +4053,7 @@
     }
 
     /** TODO(b/181707736) More gracefully handle exiting split selection state */
+    @SuppressLint("WrongCall")
     protected void resetFromSplitSelectionState() {
         if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
             if (mFirstFloatingTaskView != null) {
@@ -4433,7 +4428,8 @@
         }
 
         RemoteTargetGluer gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
-        mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
+        mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(
+                getContext(), recentsAnimationTargets);
         mSplitBoundsConfig = gluer.getStagedSplitBounds();
         // Add release check to the targets from the RemoteTargetGluer and not the targets
         // passed in because in the event we're in split screen, we use the passed in targets
@@ -4544,6 +4540,7 @@
     /**
      * Updates page scroll synchronously after measure and layout child views.
      */
+    @SuppressLint("WrongCall")
     public void updateScrollSynchronously() {
         // onMeasure is needed to update child's measured width which is used in scroll calculation,
         // in case TaskView sizes has changed when being focused/unfocused.