Merge "Reducing the time for expected launcher initialization back to 10 sec" into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index f2aa842..d5ea1ec 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -88,6 +88,4 @@
     public static RotationMode getRotationMode(DeviceProfile dp) {
         return RotationMode.NORMAL;
     }
-
-    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { }
 }
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 6b50088..04753d2 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -46,13 +46,13 @@
         RemoteAnimationProvider {
     private static final String TAG = "AppToOverviewAnimationProvider";
 
-    private final BaseActivityInterface<T> mHelper;
+    private final BaseActivityInterface<T> mActivityInterface;
     private final int mTargetTaskId;
     private IconRecentsView mRecentsView;
     private AppToOverviewAnimationListener mAnimationReadyListener;
 
-    AppToOverviewAnimationProvider(BaseActivityInterface<T> helper, int targetTaskId) {
-        mHelper = helper;
+    AppToOverviewAnimationProvider(BaseActivityInterface<T> activityInterface, int targetTaskId) {
+        mActivityInterface = activityInterface;
         mTargetTaskId = targetTaskId;
     }
 
@@ -68,15 +68,15 @@
     /**
      * Callback for when the activity is ready/initialized.
      *
-     * @param activity the activity that is ready
      * @param wasVisible true if it was visible before
      */
-    boolean onActivityReady(T activity, Boolean wasVisible) {
+    boolean onActivityReady(Boolean wasVisible) {
+        T activity = mActivityInterface.getCreatedActivity();
         if (mAnimationReadyListener != null) {
             mAnimationReadyListener.onActivityReady(activity);
         }
         BaseActivityInterface.AnimationFactory factory =
-                mHelper.prepareRecentsUI(activity, wasVisible,
+                mActivityInterface.prepareRecentsUI(wasVisible,
                         false /* animate activity */, (controller) -> {
                             controller.dispatchOnStart();
                             ValueAnimator anim = controller.getAnimationPlayer()
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 2af8441..ecb9472 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -29,8 +29,8 @@
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.views.IconRecentsView;
 
-import java.util.function.BiPredicate;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * {@link BaseActivityInterface} for recents when the default launcher is different than the
@@ -43,12 +43,13 @@
     public FallbackActivityInterface() { }
 
     @Override
-    public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
+    public AnimationFactory prepareRecentsUI(boolean activityVisible,
             boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
         if (activityVisible) {
             return (transitionLength) -> { };
         }
 
+        RecentsActivity activity = getCreatedActivity();
         IconRecentsView rv = activity.getOverviewPanel();
         rv.setUsingRemoteAnimation(true);
         rv.setAlpha(0);
@@ -84,8 +85,9 @@
 
     @Override
     public ActivityInitListener createActivityInitListener(
-            BiPredicate<RecentsActivity, Boolean> onInitListener) {
-        return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER);
+            Predicate<Boolean> onInitListener) {
+        return new ActivityInitListener<>((activity, alreadyOnHome) ->
+                onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER);
     }
 
     @Nullable
@@ -115,5 +117,5 @@
     }
 
     @Override
-    public void onLaunchTaskSuccess(RecentsActivity activity) { }
+    public void onLaunchTaskSuccess() { }
 }
diff --git a/go/quickstep/src/com/android/quickstep/GoActivityInterface.java b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java
index 5ce0f4c..b62d17c 100644
--- a/go/quickstep/src/com/android/quickstep/GoActivityInterface.java
+++ b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java
@@ -17,7 +17,7 @@
         BaseActivityInterface<T> {
 
     @Override
-    public void onTransitionCancelled(T activity, boolean activityVisible) {
+    public void onTransitionCancelled(boolean activityVisible) {
         // Go transitions to overview are all atomic.
     }
 
@@ -29,7 +29,7 @@
     }
 
     @Override
-    public void onSwipeUpToRecentsComplete(T activity) {
+    public void onSwipeUpToRecentsComplete() {
         // Go does not support swipe up gesture.
     }
 
@@ -39,7 +39,7 @@
     }
 
     @Override
-    public HomeAnimationFactory prepareHomeUI(T activity) {
+    public HomeAnimationFactory prepareHomeUI() {
         // Go does not support gestures from app to home.
         return null;
     }
@@ -63,7 +63,7 @@
     }
 
     @Override
-    public void onLaunchTaskFailed(T activity) {
+    public void onLaunchTaskFailed() {
         // Go does not support gestures from one task to another.
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 5bff8e8..3e93480 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -26,8 +26,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.views.IconRecentsView;
 
-import java.util.function.BiPredicate;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * {@link BaseActivityInterface} for the in-launcher recents.
@@ -36,15 +36,15 @@
 public final class LauncherActivityInterface extends GoActivityInterface<Launcher> {
 
     @Override
-    public AnimationFactory prepareRecentsUI(Launcher activity,
-            boolean activityVisible, boolean animateActivity,
+    public AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity,
             Consumer<AnimatorPlaybackController> callback) {
-        LauncherState fromState = activity.getStateManager().getState();
-        activity.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
+        Launcher launcher = getCreatedActivity();
+        LauncherState fromState = launcher.getStateManager().getState();
+        launcher.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
         //TODO: Implement this based off where the recents view needs to be for app => recents anim.
         return new AnimationFactory() {
             public void createActivityInterface(long transitionLength) {
-                callback.accept(activity.getStateManager().createAnimationToNewWorkspace(
+                callback.accept(launcher.getStateManager().createAnimationToNewWorkspace(
                         fromState, OVERVIEW, transitionLength));
             }
 
@@ -54,9 +54,9 @@
     }
 
     @Override
-    public LauncherInitListener createActivityInitListener(
-            BiPredicate<Launcher, Boolean> onInitListener) {
-        return new LauncherInitListener(onInitListener);
+    public LauncherInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
+        return new LauncherInitListener((activity, alreadyOnHome) ->
+                onInitListener.test(alreadyOnHome));
     }
 
     @Override
@@ -105,7 +105,8 @@
     }
 
     @Override
-    public void onLaunchTaskSuccess(Launcher launcher) {
+    public void onLaunchTaskSuccess() {
+        Launcher launcher = getCreatedActivity();
         launcher.getStateManager().moveToRestState();
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index ff73679..2a22e9d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Context;
@@ -45,7 +44,6 @@
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.views.RecentsView;
 
@@ -188,17 +186,6 @@
         return new RecentsViewStateController(launcher);
     }
 
-    /** Clears the swipe shared state for the current swipe gesture. */
-    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            launcher.<RecentsView>getOverviewPanel().switchToScreenshot(
-                    () -> TouchInteractionService.getSwipeSharedState().clearAllState(
-                            finishAnimation));
-        } else {
-            TouchInteractionService.getSwipeSharedState().clearAllState(finishAnimation);
-        }
-    }
-
     /**
      * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
      *
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 6c9f46f..1279270 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -20,13 +20,14 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.quickstep.GestureState;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
 /**
  * State to indicate we are about to launch a recent task. Note that this state is only used when
  * quick switching from launcher; quick switching from an app uses WindowTransformSwipeHelper.
- * @see com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget#NEW_TASK
+ * @see GestureState.GestureEndTarget#NEW_TASK
  */
 public class QuickSwitchState extends BackgroundAppState {
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 8a11ac8..59b117f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -70,7 +70,7 @@
         activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
         AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
         BaseActivityInterface.AnimationFactory factory =
-                mHelper.prepareRecentsUI(activity, wasVisible,
+                mHelper.prepareRecentsUI(wasVisible,
                 false /* animate activity */, (controller) -> {
                     controller.dispatchOnStart();
                     ValueAnimator anim = controller.getAnimationPlayer()
@@ -102,7 +102,7 @@
         anim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
-                mHelper.onSwipeUpToRecentsComplete(mActivity);
+                mHelper.onSwipeUpToRecentsComplete();
                 if (mRecentsView != null) {
                     mRecentsView.animateUpRunningTaskIconScale();
                 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index e1e994c..939656e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -96,6 +96,7 @@
     protected float mDragLengthFactor = 1;
 
     protected final Context mContext;
+    protected final GestureState mGestureState;
     protected final OverviewComponentObserver mOverviewComponentObserver;
     protected final BaseActivityInterface<T> mActivityInterface;
     protected final RecentsModel mRecentsModel;
@@ -129,7 +130,6 @@
 
     protected Runnable mGestureEndCallback;
 
-    protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler();
     protected MultiStateCallback mStateCallback;
 
     protected boolean mCanceled;
@@ -139,6 +139,7 @@
             OverviewComponentObserver overviewComponentObserver,
             RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
         mContext = context;
+        mGestureState = gestureState;
         mOverviewComponentObserver = overviewComponentObserver;
         mActivityInterface = gestureState.getActivityInterface();
         mRecentsModel = recentsModel;
@@ -155,14 +156,6 @@
                 .getDeviceProfile(mContext));
     }
 
-    protected void setStateOnUiThread(int stateFlag) {
-        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
-            mStateCallback.setState(stateFlag);
-        } else {
-            postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
-        }
-    }
-
     protected void performHapticFeedback() {
         if (!mVibrator.hasVibrator()) {
             return;
@@ -246,14 +239,14 @@
                                 success -> {
                                     resultCallback.accept(success);
                                     if (!success) {
-                                        mActivityInterface.onLaunchTaskFailed(mActivity);
+                                        mActivityInterface.onLaunchTaskFailed();
                                         nextTask.notifyTaskLaunchFailed(TAG);
                                     } else {
-                                        mActivityInterface.onLaunchTaskSuccess(mActivity);
+                                        mActivityInterface.onLaunchTaskSuccess();
                                     }
-                                }, mMainThreadHandler);
+                                }, MAIN_EXECUTOR.getHandler());
                     }
-                    setStateOnUiThread(successStateFlag);
+                    mStateCallback.setStateOnUiThread(successStateFlag);
                 }
                 mCanceled = false;
                 mFinishingRecentsAnimationForNewTaskId = -1;
@@ -366,7 +359,7 @@
      */
     protected abstract boolean moveWindowWithRecentsScroll();
 
-    protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome);
+    protected abstract boolean onActivityInit(Boolean alreadyOnHome);
 
     /**
      * Called to create a input proxy for the running task
@@ -394,7 +387,7 @@
     @UiThread
     public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
 
-    public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState);
+    public abstract void onConsumerAboutToBeSwitched();
 
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 8deb835..3d1ecef 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -30,6 +30,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -40,8 +41,8 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-import java.util.function.BiPredicate;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * {@link BaseActivityInterface} for recents when the default launcher is different than the
@@ -54,7 +55,7 @@
     public FallbackActivityInterface() { }
 
     @Override
-    public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
+    public void onTransitionCancelled(boolean activityVisible) {
         // TODO:
     }
 
@@ -72,7 +73,8 @@
     }
 
     @Override
-    public void onSwipeUpToRecentsComplete(RecentsActivity activity) {
+    public void onSwipeUpToRecentsComplete() {
+        RecentsActivity activity = getCreatedActivity();
         RecentsView recentsView = activity.getOverviewPanel();
         recentsView.getClearAllButton().setVisibilityAlpha(1);
         recentsView.setDisallowScrollToClearAll(false);
@@ -87,7 +89,8 @@
 
     @NonNull
     @Override
-    public HomeAnimationFactory prepareHomeUI(RecentsActivity activity) {
+    public HomeAnimationFactory prepareHomeUI() {
+        RecentsActivity activity = getCreatedActivity();
         RecentsView recentsView = activity.getOverviewPanel();
 
         return new HomeAnimationFactory() {
@@ -118,8 +121,9 @@
     }
 
     @Override
-    public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
+    public AnimationFactory prepareRecentsUI(boolean activityVisible,
             boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
+        RecentsActivity activity = getCreatedActivity();
         if (activityVisible) {
             return (transitionLength) -> { };
         }
@@ -176,8 +180,9 @@
 
     @Override
     public ActivityInitListener createActivityInitListener(
-            BiPredicate<RecentsActivity, Boolean> onInitListener) {
-        return new ActivityInitListener(onInitListener, RecentsActivity.ACTIVITY_TRACKER);
+            Predicate<Boolean> onInitListener) {
+        return new ActivityInitListener<>((activity, alreadyOnHome) ->
+                onInitListener.test(alreadyOnHome), RecentsActivity.ACTIVITY_TRACKER);
     }
 
     @Nullable
@@ -228,13 +233,15 @@
     }
 
     @Override
-    public void onLaunchTaskFailed(RecentsActivity activity) {
+    public void onLaunchTaskFailed() {
         // TODO: probably go back to overview instead.
+        RecentsActivity activity = getCreatedActivity();
         activity.<RecentsView>getOverviewPanel().startHome();
     }
 
     @Override
-    public void onLaunchTaskSuccess(RecentsActivity activity) {
+    public void onLaunchTaskSuccess() {
+        RecentsActivity activity = getCreatedActivity();
         activity.onTaskLaunched();
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index f6b3654..87db83d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -69,8 +69,8 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-import java.util.function.BiPredicate;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * {@link BaseActivityInterface} for the in-launcher recents.
@@ -92,23 +92,26 @@
     }
 
     @Override
-    public void onTransitionCancelled(Launcher activity, boolean activityVisible) {
-        LauncherState startState = activity.getStateManager().getRestState();
-        activity.getStateManager().goToState(startState, activityVisible);
+    public void onTransitionCancelled(boolean activityVisible) {
+        Launcher launcher = getCreatedActivity();
+        LauncherState startState = launcher.getStateManager().getRestState();
+        launcher.getStateManager().goToState(startState, activityVisible);
     }
 
     @Override
-    public void onSwipeUpToRecentsComplete(Launcher activity) {
+    public void onSwipeUpToRecentsComplete() {
         // Re apply state in case we did something funky during the transition.
-        activity.getStateManager().reapplyState();
-        DiscoveryBounce.showForOverviewIfNeeded(activity);
+        Launcher launcher = getCreatedActivity();
+        launcher.getStateManager().reapplyState();
+        DiscoveryBounce.showForOverviewIfNeeded(launcher);
     }
 
     @Override
-    public void onSwipeUpToHomeComplete(Launcher activity) {
+    public void onSwipeUpToHomeComplete() {
         // Ensure recents is at the correct position for NORMAL state. For example, when we detach
         // recents, we assume the first task is invisible, making translation off by one task.
-        activity.getStateManager().reapplyState();
+        Launcher launcher = getCreatedActivity();
+        launcher.getStateManager().reapplyState();
         setLauncherHideBackArrow(false);
     }
 
@@ -129,13 +132,14 @@
 
     @NonNull
     @Override
-    public HomeAnimationFactory prepareHomeUI(Launcher activity) {
-        final DeviceProfile dp = activity.getDeviceProfile();
-        final RecentsView recentsView = activity.getOverviewPanel();
+    public HomeAnimationFactory prepareHomeUI() {
+        Launcher launcher = getCreatedActivity();
+        final DeviceProfile dp = launcher.getDeviceProfile();
+        final RecentsView recentsView = launcher.getOverviewPanel();
         final TaskView runningTaskView = recentsView.getRunningTaskView();
         final View workspaceView;
         if (runningTaskView != null && runningTaskView.getTask().key.getComponent() != null) {
-            workspaceView = activity.getWorkspace().getFirstMatchForAppClose(
+            workspaceView = launcher.getWorkspace().getFirstMatchForAppClose(
                     runningTaskView.getTask().key.getComponent().getPackageName(),
                     UserHandle.of(runningTaskView.getTask().key.userId));
         } else {
@@ -144,7 +148,7 @@
         final RectF iconLocation = new RectF();
         boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
         FloatingIconView floatingIconView = canUseWorkspaceView
-                ? FloatingIconView.getFloatingIconView(activity, workspaceView,
+                ? FloatingIconView.getFloatingIconView(launcher, workspaceView,
                         true /* hideOriginal */, iconLocation, false /* isOpening */)
                 : null;
         setLauncherHideBackArrow(true);
@@ -170,14 +174,14 @@
             public AnimatorPlaybackController createActivityAnimationToHome() {
                 // Return an empty APC here since we have an non-user controlled animation to home.
                 long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
-                return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
+                return launcher.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy,
                         0 /* animComponents */);
             }
 
             @Override
             public void playAtomicAnimation(float velocity) {
                 // Setup workspace with 0 duration to prepare for our staggered animation.
-                LauncherStateManager stateManager = activity.getStateManager();
+                LauncherStateManager stateManager = launcher.getStateManager();
                 AnimatorSetBuilder builder = new AnimatorSetBuilder();
                 // setRecentsAttachedToAppWindow() will animate recents out.
                 builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
@@ -187,27 +191,28 @@
                 // Stop scrolling so that it doesn't interfere with the translation offscreen.
                 recentsView.getScroller().forceFinished(true);
 
-                new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start();
+                new StaggeredWorkspaceAnim(launcher, workspaceView, velocity).start();
             }
         };
     }
 
     @Override
-    public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
+    public AnimationFactory prepareRecentsUI(boolean activityVisible,
             boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
-        final LauncherState startState = activity.getStateManager().getState();
+        Launcher launcher = getCreatedActivity();
+        final LauncherState startState = launcher.getStateManager().getState();
 
         LauncherState resetState = startState;
         if (startState.disableRestore) {
-            resetState = activity.getStateManager().getRestState();
+            resetState = launcher.getStateManager().getRestState();
         }
-        activity.getStateManager().setRestState(resetState);
+        launcher.getStateManager().setRestState(resetState);
 
         final LauncherState fromState = animateActivity ? BACKGROUND_APP : OVERVIEW;
-        activity.getStateManager().goToState(fromState, false);
+        launcher.getStateManager().goToState(fromState, false);
         // Since all apps is not visible, we can safely reset the scroll position.
         // This ensures then the next swipe up to all-apps starts from scroll 0.
-        activity.getAppsView().reset(false /* animate */);
+        launcher.getAppsView().reset(false /* animate */);
 
         return new AnimationFactory() {
             private ShelfAnimState mShelfState;
@@ -215,11 +220,11 @@
 
             @Override
             public void createActivityInterface(long transitionLength) {
-                createActivityInterfaceInternal(activity, fromState, transitionLength, callback);
+                createActivityInterfaceInternal(launcher, fromState, transitionLength, callback);
                 // Creating the activity controller animation sometimes reapplies the launcher state
                 // (because we set the animation as the current state animation), so we reapply the
                 // attached state here as well to ensure recents is shown/hidden appropriately.
-                if (SysUINavigationMode.getMode(activity) == Mode.NO_BUTTON) {
+                if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) {
                     setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
                 }
             }
@@ -233,7 +238,7 @@
 
             @Override
             public void onTransitionCancelled() {
-                activity.getStateManager().goToState(startState, false /* animate */);
+                launcher.getStateManager().goToState(startState, false /* animate */);
             }
 
             @Override
@@ -243,15 +248,15 @@
                     return;
                 }
                 mShelfState = shelfState;
-                activity.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
+                launcher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
                 if (mShelfState == ShelfAnimState.CANCEL) {
                     return;
                 }
-                float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(activity);
-                float shelfOverviewProgress = OVERVIEW.getVerticalProgress(activity);
+                float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(launcher);
+                float shelfOverviewProgress = OVERVIEW.getVerticalProgress(launcher);
                 // Peek based on default overview progress so we can see hotseat if we're showing
                 // that instead of predictions in overview.
-                float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(activity);
+                float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(launcher);
                 float shelfPeekingProgress = shelfHiddenProgress
                         - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
                 float toProgress = mShelfState == ShelfAnimState.HIDE
@@ -259,7 +264,7 @@
                         : mShelfState == ShelfAnimState.PEEK
                                 ? shelfPeekingProgress
                                 : shelfOverviewProgress;
-                Animator shelfAnim = activity.getStateManager()
+                Animator shelfAnim = launcher.getStateManager()
                         .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
                 shelfAnim.setInterpolator(interpolator);
                 shelfAnim.setDuration(duration).start();
@@ -271,8 +276,8 @@
                     return;
                 }
                 mIsAttachedToWindow = attached;
-                LauncherRecentsView recentsView = activity.getOverviewPanel();
-                Animator fadeAnim = activity.getStateManager()
+                LauncherRecentsView recentsView = launcher.getOverviewPanel();
+                Animator fadeAnim = launcher.getStateManager()
                         .createStateElementAnimation(
                         INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
 
@@ -286,7 +291,7 @@
 
                     float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
                     float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
-                    activity.getStateManager()
+                    launcher.getStateManager()
                             .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
 
                     if (!recentsView.isShown() && animate) {
@@ -298,7 +303,7 @@
                     if (!animate) {
                         recentsView.setTranslationX(toTranslationX);
                     } else {
-                        activity.getStateManager().createStateElementAnimation(
+                        launcher.getStateManager().createStateElementAnimation(
                                 INDEX_RECENTS_TRANSLATE_X_ANIM,
                                 fromTranslationX, toTranslationX).start();
                     }
@@ -396,9 +401,9 @@
     }
 
     @Override
-    public ActivityInitListener createActivityInitListener(
-            BiPredicate<Launcher, Boolean> onInitListener) {
-        return new LauncherInitListenerEx(onInitListener);
+    public ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
+        return new LauncherInitListenerEx((activity, alreadyOnHome) ->
+                onInitListener.test(alreadyOnHome));
     }
 
     @Nullable
@@ -469,12 +474,14 @@
     }
 
     @Override
-    public void onLaunchTaskFailed(Launcher launcher) {
+    public void onLaunchTaskFailed() {
+        Launcher launcher = getCreatedActivity();
         launcher.getStateManager().goToState(OVERVIEW);
     }
 
     @Override
-    public void onLaunchTaskSuccess(Launcher launcher) {
+    public void onLaunchTaskSuccess() {
+        Launcher launcher = getCreatedActivity();
         launcher.getStateManager().moveToRestState();
     }
 
@@ -493,22 +500,21 @@
     }
 
     @Override
-    public void switchToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {
+    public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
+            Runnable onFinishRunnable) {
         Launcher launcher = getCreatedActivity();
         RecentsView recentsView = launcher.getOverviewPanel();
         if (recentsView == null) {
-            if (runnable != null) {
-                runnable.run();
+            if (onFinishRunnable != null) {
+                onFinishRunnable.run();
             }
             return;
         }
-        TaskView taskView = recentsView.getRunningTaskView();
-        if (taskView != null) {
-            taskView.setShowScreenshot(true);
-            taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
-            ViewUtils.postDraw(taskView, runnable);
-        } else if (runnable != null) {
-            runnable.run();
-        }
+        recentsView.switchToScreenshot(thumbnailData, onFinishRunnable);
+    }
+
+    @Override
+    public void setOnDeferredActivityLaunchCallback(Runnable r) {
+        getCreatedActivity().setOnDeferredActivityLaunchCallback(r);
     }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
deleted file mode 100644
index 357c9fc..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/MultiStateCallback.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2017 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.util.Log;
-import android.util.SparseArray;
-
-import com.android.launcher3.config.FeatureFlags;
-
-import java.util.StringJoiner;
-import java.util.function.Consumer;
-
-/**
- * Utility class to help manage multiple callbacks based on different states.
- */
-public class MultiStateCallback {
-
-    private static final String TAG = "MultiStateCallback";
-    public static final boolean DEBUG_STATES = false;
-
-    private final SparseArray<Runnable> mCallbacks = new SparseArray<>();
-    private final SparseArray<Consumer<Boolean>> mStateChangeHandlers = new SparseArray<>();
-
-    private final String[] mStateNames;
-
-    public MultiStateCallback(String[] stateNames) {
-        mStateNames = DEBUG_STATES ? stateNames : null;
-    }
-
-    private int mState = 0;
-
-    /**
-     * Adds the provided state flags to the global state and executes any callbacks as a result.
-     */
-    public void setState(int stateFlag) {
-        if (DEBUG_STATES) {
-            Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
-                    + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
-        }
-
-        int oldState = mState;
-        mState = mState | stateFlag;
-
-        int count = mCallbacks.size();
-        for (int i = 0; i < count; i++) {
-            int state = mCallbacks.keyAt(i);
-
-            if ((mState & state) == state) {
-                Runnable callback = mCallbacks.valueAt(i);
-                if (callback != null) {
-                    // Set the callback to null, so that it does not run again.
-                    mCallbacks.setValueAt(i, null);
-                    callback.run();
-                }
-            }
-        }
-        notifyStateChangeHandlers(oldState);
-    }
-
-    /**
-     * Adds the provided state flags to the global state and executes any change handlers
-     * as a result.
-     */
-    public void clearState(int stateFlag) {
-        if (DEBUG_STATES) {
-            Log.d(TAG, "[" + System.identityHashCode(this) + "] Removing "
-                    + convertToFlagNames(stateFlag) + " from " + convertToFlagNames(mState));
-        }
-
-        int oldState = mState;
-        mState = mState & ~stateFlag;
-        notifyStateChangeHandlers(oldState);
-    }
-
-    private void notifyStateChangeHandlers(int oldState) {
-        int count = mStateChangeHandlers.size();
-        for (int i = 0; i < count; i++) {
-            int state = mStateChangeHandlers.keyAt(i);
-            boolean wasOn = (state & oldState) == state;
-            boolean isOn = (state & mState) == state;
-
-            if (wasOn != isOn) {
-                mStateChangeHandlers.valueAt(i).accept(isOn);
-            }
-        }
-    }
-
-    /**
-     * Sets the callbacks to be run when the provided states are enabled.
-     * The callback is only run once.
-     */
-    public void addCallback(int stateMask, Runnable callback) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && mCallbacks.get(stateMask) != null) {
-            throw new IllegalStateException("Multiple callbacks on same state");
-        }
-        mCallbacks.put(stateMask, callback);
-    }
-
-    /**
-     * Sets the handler to be called when the provided states are enabled or disabled.
-     */
-    public void addChangeHandler(int stateMask, Consumer<Boolean> handler) {
-        mStateChangeHandlers.put(stateMask, handler);
-    }
-
-    public int getState() {
-        return mState;
-    }
-
-    public boolean hasStates(int stateMask) {
-        return (mState & stateMask) == stateMask;
-    }
-
-    private String convertToFlagNames(int flags) {
-        StringJoiner joiner = new StringJoiner(", ", "[", " (" + flags + ")]");
-        for (int i = 0; i < mStateNames.length; i++) {
-            if ((flags & (1 << i)) != 0) {
-                joiner.add(mStateNames[i]);
-            }
-        }
-        return joiner.toString();
-    }
-
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index 150c44d..7f53d83 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -203,7 +203,8 @@
             return false;
         }
 
-        private boolean onActivityReady(T activity, Boolean wasVisible) {
+        private boolean onActivityReady(Boolean wasVisible) {
+            final T activity = mActivityInterface.getCreatedActivity();
             if (!mUserEventLogged) {
                 activity.getUserEventDispatcher().logActionCommand(
                         LauncherLogProto.Action.Command.RECENTS_BUTTON,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
deleted file mode 100644
index cd8e1a4..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ /dev/null
@@ -1,183 +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;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.util.Log;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-
-import com.android.systemui.shared.recents.model.ThumbnailData;
-
-import java.io.PrintWriter;
-
-/**
- * Utility class used to store state information shared across multiple transitions.
- */
-public class SwipeSharedState implements RecentsAnimationListener {
-
-    private OverviewComponentObserver mOverviewComponentObserver;
-
-    private RecentsAnimationCallbacks mRecentsAnimationListener;
-    private RecentsAnimationController mLastRecentsAnimationController;
-    private RecentsAnimationTargets mLastAnimationTarget;
-
-    private boolean mLastAnimationCancelled = false;
-    private boolean mLastAnimationRunning = false;
-
-    public boolean canGestureBeContinued;
-    public boolean goingToLauncher;
-    public boolean recentsAnimationFinishInterrupted;
-    public int nextRunningTaskId = -1;
-    private int mLogId;
-
-    public void setOverviewComponentObserver(OverviewComponentObserver observer) {
-        mOverviewComponentObserver = observer;
-    }
-
-    @Override
-    public final void onRecentsAnimationStart(RecentsAnimationController controller,
-            RecentsAnimationTargets targets) {
-        mLastRecentsAnimationController = controller;
-        mLastAnimationTarget = targets;
-
-        mLastAnimationCancelled = false;
-        mLastAnimationRunning = true;
-    }
-
-    @Override
-    public final void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        if (thumbnailData != null) {
-            mOverviewComponentObserver.getActivityInterface().switchToScreenshot(thumbnailData,
-                    () -> {
-                        mLastRecentsAnimationController.cleanupScreenshot();
-                        clearAnimationState();
-                    });
-        } else {
-            clearAnimationState();
-        }
-    }
-
-    @Override
-    public final void onRecentsAnimationFinished(RecentsAnimationController controller) {
-        if (mLastRecentsAnimationController == controller) {
-            mLastAnimationRunning = false;
-        }
-    }
-
-    private void clearAnimationTarget() {
-        if (mLastAnimationTarget != null) {
-            mLastAnimationTarget.release();
-            mLastAnimationTarget = null;
-        }
-    }
-
-    private void clearAnimationState() {
-        clearAnimationTarget();
-
-        mLastAnimationCancelled = true;
-        mLastAnimationRunning = false;
-    }
-
-    private void clearListenerState(boolean finishAnimation) {
-        if (mRecentsAnimationListener != null) {
-            mRecentsAnimationListener.removeListener(this);
-            mRecentsAnimationListener.notifyAnimationCanceled();
-            if (mLastAnimationRunning && mLastRecentsAnimationController != null) {
-                Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
-                        finishAnimation
-                                ? mLastRecentsAnimationController::finishAnimationToHome
-                                : mLastRecentsAnimationController::finishAnimationToApp);
-                mLastRecentsAnimationController = null;
-                mLastAnimationTarget = null;
-            }
-        }
-        mRecentsAnimationListener = null;
-        clearAnimationTarget();
-        mLastAnimationCancelled = false;
-        mLastAnimationRunning = false;
-    }
-
-    public RecentsAnimationCallbacks newRecentsAnimationCallbacks() {
-        Preconditions.assertUIThread();
-
-        if (mLastAnimationRunning) {
-            String msg = "New animation started before completing old animation";
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
-                throw new IllegalArgumentException(msg);
-            } else {
-                Log.e("SwipeSharedState", msg, new Exception());
-            }
-        }
-
-        clearListenerState(false /* finishAnimation */);
-        boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false
-                : mOverviewComponentObserver.getActivityInterface().shouldMinimizeSplitScreen();
-        mRecentsAnimationListener = new RecentsAnimationCallbacks(shouldMinimiseSplitScreen);
-        mRecentsAnimationListener.addListener(this);
-        return mRecentsAnimationListener;
-    }
-
-    public RecentsAnimationCallbacks getActiveListener() {
-        return mRecentsAnimationListener;
-    }
-
-    public void applyActiveRecentsAnimationState(RecentsAnimationListener listener) {
-        if (mLastRecentsAnimationController != null) {
-            listener.onRecentsAnimationStart(mLastRecentsAnimationController,
-                    mLastAnimationTarget);
-        } else if (mLastAnimationCancelled) {
-            listener.onRecentsAnimationCanceled(null);
-        }
-    }
-
-    /**
-     * Called when a recents animation has finished, but was interrupted before the next task was
-     * launched. The given {@param runningTaskId} should be used as the running task for the
-     * continuing input consumer.
-     */
-    public void setRecentsAnimationFinishInterrupted(int runningTaskId) {
-        recentsAnimationFinishInterrupted = true;
-        nextRunningTaskId = runningTaskId;
-        mLastAnimationTarget = mLastAnimationTarget.cloneWithoutTargets();
-    }
-
-    public void clearAllState(boolean finishAnimation) {
-        clearListenerState(finishAnimation);
-        canGestureBeContinued = false;
-        recentsAnimationFinishInterrupted = false;
-        nextRunningTaskId = -1;
-        goingToLauncher = false;
-    }
-
-    public void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "goingToLauncher=" + goingToLauncher);
-        pw.println(prefix + "canGestureBeContinued=" + canGestureBeContinued);
-        pw.println(prefix + "recentsAnimationFinishInterrupted=" + recentsAnimationFinishInterrupted);
-        pw.println(prefix + "nextRunningTaskId=" + nextRunningTaskId);
-        pw.println(prefix + "lastAnimationCancelled=" + mLastAnimationCancelled);
-        pw.println(prefix + "lastAnimationRunning=" + mLastAnimationRunning);
-        pw.println(prefix + "logTraceId=" + mLogId);
-    }
-
-    public void setLogTraceId(int logId) {
-        this.mLogId = logId;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 0eafb44..e3fcd2f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -129,10 +129,11 @@
         public void onInitialize(Bundle bundle) {
             ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
                     bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
-            MAIN_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(TouchInteractionService.this)
-                    .setProxy(proxy));
-            MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
-            MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
+            MAIN_EXECUTOR.execute(() -> {
+                SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
+                TouchInteractionService.this.initInputMonitor();
+                preloadOverview(true /* fromInit */);
+            });
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS initialized");
             }
@@ -169,15 +170,19 @@
         @BinderThread
         @Override
         public void onAssistantAvailable(boolean available) {
-            MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantAvailable(available));
-            MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged);
+            MAIN_EXECUTOR.execute(() -> {
+                mDeviceState.setAssistantAvailable(available);
+                TouchInteractionService.this.onAssistantVisibilityChanged();
+            });
         }
 
         @BinderThread
         @Override
         public void onAssistantVisibilityChanged(float visibility) {
-            MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantVisibility(visibility));
-            MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged);
+            MAIN_EXECUTOR.execute(() -> {
+                mDeviceState.setAssistantVisibility(visibility);
+                TouchInteractionService.this.onAssistantVisibilityChanged();
+            });
         }
 
         @BinderThread
@@ -199,8 +204,10 @@
 
         @BinderThread
         public void onSystemUiStateChanged(int stateFlags) {
-            MAIN_EXECUTOR.execute(() -> mDeviceState.setSystemUiFlags(stateFlags));
-            MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
+            MAIN_EXECUTOR.execute(() -> {
+                mDeviceState.setSystemUiFlags(stateFlags);
+                TouchInteractionService.this.onSystemUiFlagsChanged();
+            });
         }
 
         @BinderThread
@@ -228,8 +235,6 @@
 
     private static boolean sConnected = false;
     private static boolean sIsInitialized = false;
-    private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState();
-    private int mLogId;
 
     public static boolean isConnected() {
         return sConnected;
@@ -239,14 +244,7 @@
         return sIsInitialized;
     }
 
-    public static SwipeSharedState getSwipeSharedState() {
-        return sSwipeSharedState;
-    }
-
-    private final InputConsumer mResetGestureInputConsumer =
-            new ResetGestureInputConsumer(sSwipeSharedState);
-
-    private final BaseSwipeUpHandler.Factory mWindowTreansformFactory =
+    private final BaseSwipeUpHandler.Factory mWindowTransformFactory =
             this::createWindowTransformSwipeHandler;
     private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory =
             this::createFallbackNoButtonSwipeHandler;
@@ -257,10 +255,13 @@
     private OverviewComponentObserver mOverviewComponentObserver;
     private InputConsumerController mInputConsumer;
     private RecentsAnimationDeviceState mDeviceState;
+    private TaskAnimationManager mTaskAnimationManager;
 
     private InputConsumer mUncheckedConsumer = InputConsumer.NO_OP;
     private InputConsumer mConsumer = InputConsumer.NO_OP;
     private Choreographer mMainChoreographer;
+    private InputConsumer mResetGestureInputConsumer;
+    private GestureState mGestureState = new GestureState();
 
     private InputMonitorCompat mInputMonitorCompat;
     private InputEventReceiver mInputEventReceiver;
@@ -331,13 +332,13 @@
 
     @UiThread
     public void onUserUnlocked() {
+        mTaskAnimationManager = new TaskAnimationManager();
         mRecentsModel = RecentsModel.INSTANCE.get(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
         mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
                 mOverviewComponentObserver);
+        mResetGestureInputConsumer = new ResetGestureInputConsumer(mTaskAnimationManager);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
-
-        sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
         mInputConsumer.registerInputConsumer();
         onSystemUiFlagsChanged();
         onAssistantVisibilityChanged();
@@ -349,6 +350,17 @@
         resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
+    private void onDeferredActivityLaunch() {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            mOverviewComponentObserver.getActivityInterface().switchRunningTaskViewToScreenshot(
+                    null, () -> {
+                        mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+                    });
+        } else {
+            mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+        }
+    }
+
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
         if (!mDeviceState.isUserUnlocked() || !mMode.hasGestures) {
             // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
@@ -422,15 +434,13 @@
         MotionEvent event = (MotionEvent) ev;
         if (event.getAction() == ACTION_DOWN) {
             GestureState newGestureState = new GestureState(
-                    mOverviewComponentObserver.getActivityInterface());
-
-            mLogId = ActiveGestureLog.INSTANCE.generateAndSetLogId();
-            sSwipeSharedState.setLogTraceId(mLogId);
+                    mOverviewComponentObserver.getActivityInterface(),
+                    ActiveGestureLog.INSTANCE.generateAndSetLogId());
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
-                boolean useSharedState = mConsumer.useSharedSwipeState();
                 mConsumer.onConsumerAboutToBeSwitched();
-                mConsumer = newConsumer(newGestureState, useSharedState, event);
+                mConsumer = newConsumer(mGestureState, newGestureState, event);
+
                 ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
                 mUncheckedConsumer = mConsumer;
             } else if (mDeviceState.isUserUnlocked() && mMode == Mode.NO_BUTTON
@@ -443,6 +453,9 @@
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
+
+            // Save the current gesture state
+            mGestureState = newGestureState;
         }
 
         ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
@@ -450,39 +463,42 @@
         TraceHelper.INSTANCE.endFlagsOverride(traceToken);
     }
 
-    private InputConsumer newConsumer(GestureState gestureState, boolean useSharedState,
-            MotionEvent event) {
+    private InputConsumer newConsumer(GestureState previousGestureState,
+            GestureState newGestureState, MotionEvent event) {
         boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
 
         if (!mDeviceState.isUserUnlocked()) {
             if (canStartSystemGesture) {
                 // This handles apps launched in direct boot mode (e.g. dialer) as well as apps
                 // launched while device is locked even after exiting direct boot mode (e.g. camera).
-                return createDeviceLockedInputConsumer(gestureState,
+                return createDeviceLockedInputConsumer(newGestureState,
                         mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
             } else {
                 return mResetGestureInputConsumer;
             }
         }
 
-        // When using sharedState, bypass systemState check as this is a followup gesture and the
-        // first gesture started in a valid system state.
-        InputConsumer base = canStartSystemGesture || useSharedState
-                ? newBaseConsumer(gestureState, useSharedState, event) : mResetGestureInputConsumer;
+        // When there is an existing recents animation running, bypass systemState check as this is
+        // a followup gesture and the first gesture started in a valid system state.
+        InputConsumer base = canStartSystemGesture
+                || previousGestureState.isRecentsAnimationRunning()
+                        ? newBaseConsumer(previousGestureState, newGestureState, event)
+                        : mResetGestureInputConsumer;
         if (mMode == Mode.NO_BUTTON) {
             if (mDeviceState.canTriggerAssistantAction(event)) {
-                base = new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat);
+                base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
             }
 
             if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
                 // Put the Compose gesture as higher priority than the Assistant or base gestures
-                base = new QuickCaptureInputConsumer(this, gestureState, base, mInputMonitorCompat);
+                base = new QuickCaptureInputConsumer(this, newGestureState, base,
+                        mInputMonitorCompat);
             }
 
             if (mDeviceState.isScreenPinningActive()) {
                 // Note: we only allow accessibility to wrap this, and it replaces the previous
                 // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
-                base = new ScreenPinnedInputConsumer(this, gestureState);
+                base = new ScreenPinnedInputConsumer(this, newGestureState);
             }
 
             if (mDeviceState.isAccessibilityMenuAvailable()) {
@@ -497,13 +513,10 @@
         return base;
     }
 
-    private InputConsumer newBaseConsumer(GestureState gestureState, boolean useSharedState,
-            MotionEvent event) {
+    private InputConsumer newBaseConsumer(GestureState previousGestureState,
+            GestureState gestureState, MotionEvent event) {
         RunningTaskInfo runningTaskInfo = TraceHelper.whitelistIpcs("getRunningTask.0",
                 () -> mAM.getRunningTask(0));
-        if (!useSharedState) {
-            sSwipeSharedState.clearAllState(false /* finishAnimation */);
-        }
         if (mDeviceState.isKeyguardShowingOccluded()) {
             // This handles apps showing over the lockscreen (e.g. camera)
             return createDeviceLockedInputConsumer(gestureState, runningTaskInfo);
@@ -524,26 +537,27 @@
             }
         }
 
-        if (runningTaskInfo == null && !sSwipeSharedState.goingToLauncher
-                && !sSwipeSharedState.recentsAnimationFinishInterrupted) {
-            return mResetGestureInputConsumer;
-        } else if (sSwipeSharedState.recentsAnimationFinishInterrupted) {
+        if (previousGestureState.getFinishingRecentsAnimationTaskId() > 0) {
             // If the finish animation was interrupted, then continue using the other activity input
             // consumer but with the next task as the running task
             RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
-            info.id = sSwipeSharedState.nextRunningTaskId;
-            return createOtherActivityInputConsumer(gestureState, event, info);
-        } else if (sSwipeSharedState.goingToLauncher
+            info.id = previousGestureState.getFinishingRecentsAnimationTaskId();
+            return createOtherActivityInputConsumer(previousGestureState, gestureState, event,
+                    info);
+        } else if (runningTaskInfo == null) {
+            return mResetGestureInputConsumer;
+        } else if (previousGestureState.isRunningAnimationToLauncher()
                 || gestureState.getActivityInterface().isResumed()
                 || forceOverviewInputConsumer) {
-            return createOverviewInputConsumer(gestureState, event);
+            return createOverviewInputConsumer(previousGestureState, gestureState, event);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
                 && gestureState.getActivityInterface().isInLiveTileMode()) {
-            return createOverviewInputConsumer(gestureState, event);
+            return createOverviewInputConsumer(previousGestureState, gestureState, event);
         } else if (mDeviceState.isGestureBlockedActivity(runningTaskInfo)) {
             return mResetGestureInputConsumer;
         } else {
-            return createOtherActivityInputConsumer(gestureState, event, runningTaskInfo);
+            return createOtherActivityInputConsumer(previousGestureState, gestureState, event,
+                    runningTaskInfo);
         }
     }
 
@@ -553,44 +567,47 @@
                 && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
     }
 
-    private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
+    private InputConsumer createOtherActivityInputConsumer(GestureState previousGestureState,
+            GestureState gestureState,
             MotionEvent event, RunningTaskInfo runningTaskInfo) {
 
         final boolean shouldDefer;
         final BaseSwipeUpHandler.Factory factory;
 
         if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
-            shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
+            shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
             factory = mFallbackNoButtonFactory;
         } else {
             shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
                     event);
-            factory = mWindowTreansformFactory;
+            factory = mWindowTransformFactory;
         }
 
         final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
-        return new OtherActivityInputConsumer(this, mDeviceState, gestureState, runningTaskInfo,
-                shouldDefer, this::onConsumerInactive, sSwipeSharedState, mInputMonitorCompat,
-                disableHorizontalSwipe, factory, mLogId);
+        return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager,
+                gestureState, runningTaskInfo, shouldDefer, this::onConsumerInactive,
+                mInputMonitorCompat, disableHorizontalSwipe, factory);
     }
 
     private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState,
             RunningTaskInfo taskInfo) {
         if (mMode == Mode.NO_BUTTON && taskInfo != null) {
-            return new DeviceLockedInputConsumer(this, mDeviceState, gestureState,
-                    sSwipeSharedState, mInputMonitorCompat, taskInfo.taskId, mLogId);
+            return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
+                    gestureState, mInputMonitorCompat, taskInfo.taskId);
         } else {
             return mResetGestureInputConsumer;
         }
     }
 
-    public InputConsumer createOverviewInputConsumer(GestureState gestureState, MotionEvent event) {
+    public InputConsumer createOverviewInputConsumer(GestureState previousGestureState,
+            GestureState gestureState, MotionEvent event) {
         BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity();
         if (activity == null) {
             return mResetGestureInputConsumer;
         }
 
-        if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
+        if (activity.getRootView().hasWindowFocus()
+                || previousGestureState.isRunningAnimationToLauncher()) {
             return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
@@ -629,8 +646,8 @@
                 mOverviewComponentObserver.getActivityInterface();
         if (activityInterface.getCreatedActivity() == null) {
             // Make sure that UI states will be initialized.
-            activityInterface.createActivityInitListener((activity, wasVisible) -> {
-                AppLaunchTracker.INSTANCE.get(activity);
+            activityInterface.createActivityInitListener((wasVisible) -> {
+                AppLaunchTracker.INSTANCE.get(TouchInteractionService.this);
                 return false;
             }).register();
         } else if (fromInit) {
@@ -640,9 +657,8 @@
             return;
         }
 
-        // Pass null animation handler to indicate this start is preload.
-        startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(),
-                null);
+        mTaskAnimationManager.preloadRecentsAnimation(
+                mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
     }
 
     @Override
@@ -686,10 +702,6 @@
             boolean resumed = mOverviewComponentObserver != null
                     && mOverviewComponentObserver.getActivityInterface().isResumed();
             pw.println("  resumed=" + resumed);
-            pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
-            if (mConsumer.useSharedSwipeState()) {
-                sSwipeSharedState.dump("    ", pw);
-            }
             pw.println("  mConsumer=" + mConsumer.getName());
             pw.println("FeatureFlags:");
             pw.println("  APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get());
@@ -718,9 +730,9 @@
     private BaseSwipeUpHandler createWindowTransformSwipeHandler(GestureState gestureState,
             RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
             boolean isLikelyToStartNewTask) {
-        return  new WindowTransformSwipeHandler(this, mDeviceState, gestureState, runningTask,
-                touchTimeMs, mOverviewComponentObserver, continuingLastGesture, mInputConsumer,
-                mRecentsModel);
+        return  new WindowTransformSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+                gestureState, runningTask, touchTimeMs, mOverviewComponentObserver,
+                continuingLastGesture, mInputConsumer, mRecentsModel);
     }
 
     private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 1168758..22ad180 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -26,11 +26,12 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.HIDE;
 import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.PEEK;
+import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
+import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK;
-import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
@@ -70,6 +71,7 @@
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
+import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -138,42 +140,6 @@
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
-    public enum GestureEndTarget {
-        HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false,
-                ContainerType.WORKSPACE, false),
-
-        RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
-                | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true),
-
-        NEW_TASK(0, STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT, false, true,
-                ContainerType.APP, true),
-
-        LAST_TASK(0, STATE_RESUME_LAST_TASK, false, true, ContainerType.APP, false);
-
-        GestureEndTarget(float endShift, int endState, boolean isLauncher, boolean canBeContinued,
-                int containerType, boolean recentsAttachedToAppWindow) {
-            this.endShift = endShift;
-            this.endState = endState;
-            this.isLauncher = isLauncher;
-            this.canBeContinued = canBeContinued;
-            this.containerType = containerType;
-            this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
-        }
-
-        /** 0 is app, 1 is overview */
-        public final float endShift;
-        /** The state to apply when we reach this final target */
-        public final int endState;
-        /** Whether the target is in the launcher activity */
-        public final boolean isLauncher;
-        /** Whether the user can start a new gesture while this one is finishing */
-        public final boolean canBeContinued;
-        /** Used to log where the user ended up after the gesture ends */
-        public final int containerType;
-        /** Whether RecentsView should be attached to the window as we animate to this target */
-        public final boolean recentsAttachedToAppWindow;
-    }
-
     public static final long MAX_SWIPE_DURATION = 350;
     public static final long MIN_SWIPE_DURATION = 80;
     public static final long MIN_OVERSHOOT_DURATION = 120;
@@ -192,9 +158,9 @@
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
     private final RecentsAnimationDeviceState mDeviceState;
+    private final TaskAnimationManager mTaskAnimationManager;
     private final GestureState mGestureState;
 
-    private GestureEndTarget mGestureEndTarget;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
     private boolean mIsShelfPeeking;
@@ -225,12 +191,17 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
+    private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
+
     public WindowTransformSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, RunningTaskInfo runningTaskInfo, long touchTimeMs,
+            TaskAnimationManager taskAnimationManager, GestureState gestureState,
+            RunningTaskInfo runningTaskInfo, long touchTimeMs,
             OverviewComponentObserver overviewComponentObserver, boolean continuingLastGesture,
             InputConsumerController inputConsumer, RecentsModel recentsModel) {
-        super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+        super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer,
+                runningTaskInfo.id);
         mDeviceState = deviceState;
+        mTaskAnimationManager = taskAnimationManager;
         mGestureState = gestureState;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
@@ -240,62 +211,65 @@
     private void initStateCallbacks() {
         mStateCallback = new MultiStateCallback(STATE_NAMES);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
+        mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
                 this::onLauncherPresentAndGestureStarted);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
+        mStateCallback.runOnceAtState(STATE_LAUNCHER_DRAWN | STATE_GESTURE_STARTED,
                 this::initializeLauncherAnimationController);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
+        mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
                 this::launcherFrameDrawn);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
+        mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
                         | STATE_GESTURE_CANCELLED,
                 this::resetStateForAnimationCancel);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
+        mStateCallback.runOnceAtState(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
                 this::sendRemoteAnimationsToAnimationFactory);
 
-        mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
+        mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
                 this::resumeLastTask);
-        mStateCallback.addCallback(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
+        mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
                 this::startNewTask);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+        mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
                         | STATE_LAUNCHER_DRAWN | STATE_CAPTURE_SCREENSHOT,
                 this::switchToScreenshot);
 
-        mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+        mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
                         | STATE_SCALED_CONTROLLER_RECENTS,
                 this::finishCurrentTransitionToRecents);
 
-        mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+        mStateCallback.runOnceAtState(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
                         | STATE_SCALED_CONTROLLER_HOME,
                 this::finishCurrentTransitionToHome);
-        mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
+        mStateCallback.runOnceAtState(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED,
                 this::reset);
 
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
+        mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
                         | STATE_LAUNCHER_DRAWN | STATE_SCALED_CONTROLLER_RECENTS
                         | STATE_CURRENT_TASK_FINISHED | STATE_GESTURE_COMPLETED
                         | STATE_GESTURE_STARTED,
                 this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
 
-        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
-        mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, this::onEndTargetSet);
+
+        mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
+        mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                 this::invalidateHandlerWithLauncher);
-        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
+        mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
                 this::notifyTransitionCancelled);
 
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
+            mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
                     (b) -> mRecentsView.setRunningTaskHidden(!b));
         }
     }
 
     @Override
-    protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+    protected boolean onActivityInit(Boolean alreadyOnHome) {
+        final T activity = mActivityInterface.getCreatedActivity();
         if (mActivity == activity) {
             return true;
         }
@@ -332,7 +306,7 @@
 
     @Override
     protected boolean moveWindowWithRecentsScroll() {
-        return mGestureEndTarget != HOME;
+        return mGestureState.getEndTarget() != HOME;
     }
 
     private void onLauncherStart(final T activity) {
@@ -345,9 +319,9 @@
 
         // If we've already ended the gesture and are going home, don't prepare recents UI,
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
-        if (mGestureEndTarget != HOME) {
+        if (mGestureState.getEndTarget() != HOME) {
             Runnable initAnimFactory = () -> {
-                mAnimationFactory = mActivityInterface.prepareRecentsUI(mActivity,
+                mAnimationFactory = mActivityInterface.prepareRecentsUI(
                         mWasLauncherAlreadyVisible, true,
                         this::onAnimatorPlaybackControllerCreated);
                 maybeUpdateRecentsAttachedState(false /* animate */);
@@ -356,7 +330,7 @@
                 // Launcher is visible, but might be about to stop. Thus, if we prepare recents
                 // now, it might get overridden by moveToRestState() in onStop(). To avoid this,
                 // wait until the next gesture (and possibly launcher) starts.
-                mStateCallback.addCallback(STATE_GESTURE_STARTED, initAnimFactory);
+                mStateCallback.runOnceAtState(STATE_GESTURE_STARTED, initAnimFactory);
             } else {
                 initAnimFactory.run();
             }
@@ -400,9 +374,25 @@
         // that time by a previous window transition.
         setupRecentsViewUi();
 
+        // For the duration of the gesture, in cases where an activity is launched while the
+        // activity is not yet resumed, finish the animation to ensure we get resumed
+        mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback(
+                mOnDeferredActivityLaunch);
+
         notifyGestureStartedAsync();
     }
 
+    private void onDeferredActivityLaunch() {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            mOverviewComponentObserver.getActivityInterface().switchRunningTaskViewToScreenshot(
+                    null, () -> {
+                        mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+                    });
+        } else {
+            mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
+        }
+    }
+
     private void setupRecentsViewUi() {
         if (mContinuingLastGesture) {
             updateSysUiFlags(mCurrentShift.value);
@@ -437,13 +427,6 @@
                 .getHighResLoadingState().setVisible(true);
     }
 
-    private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
-        float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 +
-                mContext.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
-        float interpolation = Math.min(1, offsetX / distanceToReachEdge);
-        return TaskView.getCurveScaleForInterpolation(interpolation);
-    }
-
     @Override
     public void onMotionPauseChanged(boolean isPaused) {
         setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
@@ -468,9 +451,8 @@
                 ? null
                 : mRecentsAnimationTargets.findTask(mRunningTaskId);
         final boolean recentsAttachedToAppWindow;
-        int runningTaskIndex = mRecentsView.getRunningTaskIndex();
-        if (mGestureEndTarget != null) {
-            recentsAttachedToAppWindow = mGestureEndTarget.recentsAttachedToAppWindow;
+        if (mGestureState.getEndTarget() != null) {
+            recentsAttachedToAppWindow = mGestureState.getEndTarget().recentsAttachedToAppWindow;
         } else if (mContinuingLastGesture
                 && mRecentsView.getRunningTaskIndex() != mRecentsView.getNextPage()) {
             recentsAttachedToAppWindow = true;
@@ -517,9 +499,10 @@
     }
 
     private void buildAnimationController() {
-        if (mGestureEndTarget == HOME || mHasLauncherTransitionControllerStarted) {
-            // We don't want a new mLauncherTransitionController if mGestureEndTarget == HOME (it
-            // has its own animation) or if we're already animating the current controller.
+        if (mGestureState.getEndTarget() == HOME || mHasLauncherTransitionControllerStarted) {
+            // We don't want a new mLauncherTransitionController if
+            // mGestureState.getEndTarget() == HOME (it has its own animation) or if we're already
+            // animating the current controller.
             return;
         }
         initTransitionEndpoints(mActivity.getDeviceProfile());
@@ -576,7 +559,7 @@
     }
 
     private void updateLauncherTransitionProgress() {
-        if (mGestureEndTarget == HOME) {
+        if (mGestureState.getEndTarget() == HOME) {
             return;
         }
         // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
@@ -613,9 +596,9 @@
         super.onRecentsAnimationStart(controller, targets);
 
         // Only add the callback to enable the input consumer after we actually have the controller
-        mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
+        mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
                 mRecentsAnimationController::enableInputConsumer);
-        setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+        mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
         mPassedOverviewThreshold = false;
     }
@@ -625,7 +608,7 @@
         super.onRecentsAnimationCanceled(thumbnailData);
         mRecentsView.setRecentsAnimationTargets(null, null);
         mActivityInitListener.unregister();
-        setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+        mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
         ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
     }
 
@@ -633,7 +616,7 @@
     public void onGestureStarted() {
         notifyGestureStartedAsync();
         mShiftAtGestureStart = mCurrentShift.value;
-        setStateOnUiThread(STATE_GESTURE_STARTED);
+        mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
         mGestureStarted = true;
     }
 
@@ -656,7 +639,7 @@
     @Override
     public void onGestureCancelled() {
         updateDisplacement(0);
-        setStateOnUiThread(STATE_GESTURE_COMPLETED);
+        mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
         mLogAction = Touch.SWIPE_NOOP;
         handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
     }
@@ -671,7 +654,7 @@
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
         boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
-        setStateOnUiThread(STATE_GESTURE_COMPLETED);
+        mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
 
         mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
         boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
@@ -686,7 +669,7 @@
 
     @Override
     protected InputConsumer createNewInputProxyHandler() {
-        endRunningWindowAnim(mGestureEndTarget == HOME /* cancel */);
+        endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
         endLauncherTransitionController();
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // Hide the task view, if not already hidden
@@ -708,6 +691,24 @@
         }
     }
 
+    private void onEndTargetSet() {
+        switch (mGestureState.getEndTarget()) {
+            case HOME:
+                mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
+                break;
+            case RECENTS:
+                mStateCallback.setState(STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
+                        | STATE_SCREENSHOT_VIEW_SHOWN);
+                break;
+            case NEW_TASK:
+                mStateCallback.setState(STATE_START_NEW_TASK | STATE_CAPTURE_SCREENSHOT);
+                break;
+            case LAST_TASK:
+                mStateCallback.setState(STATE_RESUME_LAST_TASK);
+                break;
+        }
+    }
+
     private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling,
             boolean isCancel) {
         final GestureEndTarget endTarget;
@@ -777,7 +778,7 @@
         float currentShift = mCurrentShift.value;
         final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
                 isFling, isCancel);
-        float endShift = endTarget.endShift;
+        float endShift = endTarget.isLauncher ? 1 : 0;
         final float startShift;
         Interpolator interpolator = DEACCEL;
         if (!isFling) {
@@ -880,14 +881,15 @@
     @UiThread
     private void animateToProgressInternal(float start, float end, long duration,
             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
-        mGestureEndTarget = target;
+        // Set the state, but don't notify until the animation completes
+        mGestureState.setEndTarget(target, false /* isAtomic */);
 
         maybeUpdateRecentsAttachedState();
 
-        if (mGestureEndTarget == HOME) {
+        if (mGestureState.getEndTarget() == HOME) {
             HomeAnimationFactory homeAnimFactory;
             if (mActivity != null) {
-                homeAnimFactory = mActivityInterface.prepareHomeUI(mActivity);
+                homeAnimFactory = mActivityInterface.prepareHomeUI();
             } else {
                 homeAnimFactory = new HomeAnimationFactory() {
                     @NonNull
@@ -904,14 +906,15 @@
                         return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
                     }
                 };
-                mStateCallback.addChangeHandler(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
+                mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
                         isPresent -> mRecentsView.startHome());
             }
             RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
             windowAnim.addAnimatorListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
-                    setStateOnUiThread(target.endState);
+                    // Finalize the state and notify of the change
+                    mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
                 }
             });
             windowAnim.start(velocityPxPerMs);
@@ -936,10 +939,9 @@
                         // We are about to launch the current running task, so use LAST_TASK state
                         // instead of NEW_TASK. This could happen, for example, if our scroll is
                         // aborted after we determined the target to be NEW_TASK.
-                        setStateOnUiThread(LAST_TASK.endState);
-                    } else {
-                        setStateOnUiThread(target.endState);
+                        mGestureState.setEndTarget(LAST_TASK);
                     }
+                    mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
                 }
             });
             windowAnim.start();
@@ -947,7 +949,7 @@
         }
         // Always play the entire launcher animation when going home, since it is separate from
         // the animation that has been controlled thus far.
-        if (mGestureEndTarget == HOME) {
+        if (mGestureState.getEndTarget() == HOME) {
             start = 0;
         }
 
@@ -999,21 +1001,16 @@
                 }
                 // Make sure recents is in its final state
                 maybeUpdateRecentsAttachedState(false);
-                mActivityInterface.onSwipeUpToHomeComplete(mActivity);
+                mActivityInterface.onSwipeUpToHomeComplete();
             }
         });
         return anim;
     }
 
     @Override
-    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
-        if (mGestureEndTarget != null) {
-            sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued;
-            sharedState.goingToLauncher = mGestureEndTarget.isLauncher;
-        }
-
-        if (sharedState.canGestureBeContinued) {
-            cancelCurrentAnimation(sharedState);
+    public void onConsumerAboutToBeSwitched() {
+        if (!mGestureState.isRunningAnimationToLauncher()) {
+            cancelCurrentAnimation();
         } else {
             reset();
         }
@@ -1045,14 +1042,14 @@
     }
 
     private void reset() {
-        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+        mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
     /**
      * Cancels any running animation so that the active target can be overriden by a new swipe
      * handle (in case of quick switch).
      */
-    private void cancelCurrentAnimation(SwipeSharedState sharedState) {
+    private void cancelCurrentAnimation() {
         mCanceled = true;
         mCurrentShift.cancelAnimation();
         if (mLauncherTransitionController != null && mLauncherTransitionController
@@ -1070,7 +1067,7 @@
                     ? newRunningTaskView.getTask().key.id
                     : -1;
             mRecentsView.setCurrentTask(newRunningTaskId);
-            sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
+            mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
         }
     }
 
@@ -1090,6 +1087,8 @@
 
         mRecentsView.onGestureAnimationEnd();
 
+        // Reset the callback for deferred activity launches
+        mActivityInterface.setOnDeferredActivityLaunchCallback(null);
         mActivity.getRootView().setOnApplyWindowInsetsListener(null);
         removeLiveTileOverlay();
     }
@@ -1108,7 +1107,7 @@
 
     private void resetStateForAnimationCancel() {
         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
-        mActivityInterface.onTransitionCancelled(mActivity, wasVisible);
+        mActivityInterface.onTransitionCancelled(wasVisible);
 
         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
         mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
@@ -1123,10 +1122,10 @@
                 }
                 mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */);
             }
-            setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+            mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else if (!hasTargets()) {
             // If there are no targets, then we don't need to capture anything
-            setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+            mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else {
             boolean finishTransitionPosted = false;
             if (mRecentsAnimationController != null) {
@@ -1135,7 +1134,7 @@
                     mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
                 }
                 final TaskView taskView;
-                if (mGestureEndTarget == HOME) {
+                if (mGestureState.getEndTarget() == HOME) {
                     // Capture the screenshot before finishing the transition to home to ensure it's
                     // taken in the correct orientation, but no need to update the thumbnail.
                     taskView = null;
@@ -1146,14 +1145,15 @@
                     // Defer finishing the animation until the next launcher frame with the
                     // new thumbnail
                     finishTransitionPosted = ViewUtils.postDraw(taskView,
-                            () -> setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), this::isCanceled);
+                            () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
+                                    this::isCanceled);
                 }
             }
             if (!finishTransitionPosted) {
                 // If we haven't posted a draw callback, set the state immediately.
                 Object traceToken = TraceHelper.INSTANCE.beginSection(SCREENSHOT_CAPTURED_EVT,
                         TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS);
-                setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+                mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
                 TraceHelper.INSTANCE.endSection(traceToken);
             }
         }
@@ -1161,14 +1161,14 @@
 
     private void finishCurrentTransitionToRecents() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+            mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else if (!hasTargets()) {
             // If there are no targets, then there is nothing to finish
-            setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+            mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else {
             synchronized (mRecentsAnimationController) {
                 mRecentsAnimationController.finish(true /* toRecents */,
-                        () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
+                        () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
             }
         }
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
@@ -1177,7 +1177,7 @@
     private void finishCurrentTransitionToHome() {
         synchronized (mRecentsAnimationController) {
             mRecentsAnimationController.finish(true /* toRecents */,
-                    () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
+                    () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
                     true /* sendUserLeaveHint */);
         }
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
@@ -1186,7 +1186,7 @@
 
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
-        mActivityInterface.onSwipeUpToRecentsComplete(mActivity);
+        mActivityInterface.onSwipeUpToRecentsComplete();
         if (mRecentsAnimationController != null) {
             mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
                     true /* screenshot */);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 0b5129c..2f73fc1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -23,11 +23,6 @@
     }
 
     @Override
-    public boolean useSharedSwipeState() {
-        return mDelegate.useSharedSwipeState();
-    }
-
-    @Override
     public boolean allowInterceptByParent() {
         return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 12b7c26..370f161 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
 import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
@@ -45,9 +44,9 @@
 import com.android.quickstep.MultiStateCallback;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.SwipeSharedState;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationTargets;
+import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -76,14 +75,13 @@
 
     private final Context mContext;
     private final RecentsAnimationDeviceState mDeviceState;
+    private final TaskAnimationManager mTaskAnimationManager;
     private final GestureState mGestureState;
     private final float mTouchSlopSquared;
-    private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
 
     private final PointF mTouchDown = new PointF();
     private final AppWindowAnimationHelper mAppWindowAnimationHelper;
-    private int mLogId;
     private final AppWindowAnimationHelper.TransformParams mTransformParams;
     private final Point mDisplaySize;
     private final MultiStateCallback mStateCallback;
@@ -98,15 +96,14 @@
     private RecentsAnimationTargets mRecentsAnimationTargets;
 
     public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, SwipeSharedState swipeSharedState,
-            InputMonitorCompat inputMonitorCompat, int runningTaskId, int logId) {
+            TaskAnimationManager taskAnimationManager, GestureState gestureState,
+            InputMonitorCompat inputMonitorCompat, int runningTaskId) {
         mContext = context;
         mDeviceState = deviceState;
+        mTaskAnimationManager = taskAnimationManager;
         mGestureState = gestureState;
         mTouchSlopSquared = squaredTouchSlop(context);
-        mSwipeSharedState = swipeSharedState;
         mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
-        mLogId = logId;
         mTransformParams = new AppWindowAnimationHelper.TransformParams();
         mInputMonitorCompat = inputMonitorCompat;
         mRunningTaskId = runningTaskId;
@@ -116,7 +113,7 @@
 
         // Init states
         mStateCallback = new MultiStateCallback(STATE_NAMES);
-        mStateCallback.addCallback(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
+        mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED,
                 this::endRemoteAnimation);
 
         mVelocityTracker = VelocityTracker.obtain();
@@ -207,16 +204,14 @@
 
     private void startRecentsTransition() {
         mThresholdCrossed = true;
-        RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks();
-        callbacks.addListener(this);
+        mInputMonitorCompat.pilferPointers();
+
         Intent intent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_DEFAULT)
                 .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
-                .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
-
-        mInputMonitorCompat.pilferPointers();
-        startRecentsActivityAsync(intent, callbacks);
+                .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
+        mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
index 370b487..152b9c9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -15,14 +15,14 @@
  */
 package com.android.quickstep.inputconsumers;
 
+import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
+import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
+import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
 import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
 import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
-import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME;
-import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK;
-import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK;
-import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 
 import android.animation.Animator;
@@ -35,6 +35,7 @@
 import android.graphics.RectF;
 import android.os.Bundle;
 
+import android.util.ArrayMap;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -43,13 +44,13 @@
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseSwipeUpHandler;
 import com.android.quickstep.GestureState;
+import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.MultiStateCallback;
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsModel;
-import com.android.quickstep.SwipeSharedState;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.RecentsAnimationTargets;
@@ -83,27 +84,23 @@
     private static final int STATE_APP_CONTROLLER_RECEIVED =
             getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
 
-    public enum GestureEndTarget {
-        HOME(3, 100, 1),
-        RECENTS(1, 300, 0),
-        LAST_TASK(0, 150, 1),
-        NEW_TASK(0, 150, 1);
-
+    public static class EndTargetAnimationParams {
         private final float mEndProgress;
         private final long mDurationMultiplier;
         private final float mLauncherAlpha;
 
-        GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) {
+        EndTargetAnimationParams(float endProgress, long durationMultiplier, float launcherAlpha) {
             mEndProgress = endProgress;
             mDurationMultiplier = durationMultiplier;
             mLauncherAlpha = launcherAlpha;
         }
     }
+    private static ArrayMap<GestureEndTarget, EndTargetAnimationParams>
+            mEndTargetAnimationParams = new ArrayMap();
 
     private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
 
     private boolean mIsMotionPaused = false;
-    private GestureEndTarget mEndTarget;
 
     private final boolean mInQuickSwitchMode;
     private final boolean mContinuingLastGesture;
@@ -136,40 +133,46 @@
             mAppWindowAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
         }
 
+        // Going home has an extra long progress to ensure that it animates into the screen
+        mEndTargetAnimationParams.put(HOME, new EndTargetAnimationParams(3, 100, 1));
+        mEndTargetAnimationParams.put(RECENTS, new EndTargetAnimationParams(1, 300, 0));
+        mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1));
+        mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1));
+
         initStateCallbacks();
     }
 
     private void initStateCallbacks() {
         mStateCallback = new MultiStateCallback(STATE_NAMES);
 
-        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
+        mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
                 this::onHandlerInvalidated);
-        mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
+        mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
                 this::onHandlerInvalidatedWithRecents);
 
-        mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
+        mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
                 this::finishAnimationTargetSetAnimationComplete);
 
         if (mInQuickSwitchMode) {
-            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
+            mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
                             | STATE_RECENTS_PRESENT,
                     this::finishAnimationTargetSet);
         } else {
-            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
+            mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
                     this::finishAnimationTargetSet);
         }
     }
 
     private void onLauncherAlphaChanged() {
-        if (mRecentsAnimationTargets != null && mEndTarget == null) {
+        if (mRecentsAnimationTargets != null && mGestureState.getEndTarget() == null) {
             applyTransformUnchecked();
         }
     }
 
     @Override
-    protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) {
-        mActivity = activity;
-        mRecentsView = activity.getOverviewPanel();
+    protected boolean onActivityInit(Boolean alreadyOnHome) {
+        mActivity = mActivityInterface.getCreatedActivity();
+        mRecentsView = mActivity.getOverviewPanel();
         linkRecentsViewScroll();
         mRecentsView.setDisallowScrollToClearAll(true);
         mRecentsView.getClearAllButton().setVisibilityAlpha(0);
@@ -183,7 +186,7 @@
                 mRecentsView.onGestureAnimationStart(mRunningTaskId);
             }
         }
-        setStateOnUiThread(STATE_RECENTS_PRESENT);
+        mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
         return true;
     }
 
@@ -247,8 +250,8 @@
     @Override
     public void onGestureCancelled() {
         updateDisplacement(0);
-        mEndTarget = LAST_TASK;
-        setStateOnUiThread(STATE_GESTURE_CANCELLED);
+        mGestureState.setEndTarget(LAST_TASK);
+        mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED);
     }
 
     @Override
@@ -256,28 +259,29 @@
         mEndVelocityPxPerMs.set(0, velocity.y / 1000);
         if (mInQuickSwitchMode) {
             // For now set it to non-null, it will be reset before starting the animation
-            mEndTarget = LAST_TASK;
+            mGestureState.setEndTarget(LAST_TASK);
         } else {
             float flingThreshold = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_threshold_velocity);
             boolean isFling = Math.abs(endVelocity) > flingThreshold;
 
             if (isFling) {
-                mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
+                mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
             } else if (mIsMotionPaused) {
-                mEndTarget = RECENTS;
+                mGestureState.setEndTarget(RECENTS);
             } else {
-                mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
+                mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
+                        ? HOME
+                        : LAST_TASK);
             }
         }
-        setStateOnUiThread(STATE_GESTURE_COMPLETED);
+        mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
     }
 
     @Override
-    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
-        if (mInQuickSwitchMode && mEndTarget != null) {
-            sharedState.canGestureBeContinued = true;
-            sharedState.goingToLauncher = false;
+    public void onConsumerAboutToBeSwitched() {
+        if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) {
+            mGestureState.setEndTarget(HOME);
 
             mCanceled = true;
             mCurrentShift.cancelAnimation();
@@ -293,12 +297,12 @@
                             ? newRunningTaskView.getTask().key.id
                             : -1;
                     mRecentsView.setCurrentTask(newRunningTaskId);
-                    sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
+                    mGestureState.setFinishingRecentsAnimationTaskId(newRunningTaskId);
                 }
                 mRecentsView.setOnScrollChangeListener(null);
             }
         } else {
-            setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+            mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
         }
     }
 
@@ -319,7 +323,7 @@
     }
 
     private void finishAnimationTargetSetAnimationComplete() {
-        switch (mEndTarget) {
+        switch (mGestureState.getEndTarget()) {
             case HOME: {
                 if (mSwipeUpOverHome) {
                     mRecentsAnimationController.finish(false, null, false);
@@ -362,7 +366,7 @@
             }
         }
 
-        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+        mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
     private void finishAnimationTargetSet() {
@@ -370,17 +374,20 @@
             // Recalculate the end target, some views might have been initialized after
             // gesture has ended.
             if (mRecentsView == null || !hasTargets()) {
-                mEndTarget = LAST_TASK;
+                mGestureState.setEndTarget(LAST_TASK);
             } else {
                 final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
                 final int taskToLaunch = mRecentsView.getNextPage();
-                mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
-                        ? NEW_TASK : LAST_TASK;
+                mGestureState.setEndTarget(
+                        (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
+                                ? NEW_TASK
+                                : LAST_TASK);
             }
         }
 
-        float endProgress = mEndTarget.mEndProgress;
-        long duration = (long) (mEndTarget.mDurationMultiplier *
+        EndTargetAnimationParams params = mEndTargetAnimationParams.get(mGestureState.getEndTarget());
+        float endProgress = params.mEndProgress;
+        long duration = (long) (params.mDurationMultiplier *
                 Math.abs(endProgress - mCurrentShift.value));
         if (mRecentsView != null) {
             duration = Math.max(duration, mRecentsView.getScroller().getDuration());
@@ -395,7 +402,7 @@
                 }
             };
 
-            if (mEndTarget == HOME && !mRunningOverHome) {
+            if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) {
                 RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
                 anim.addAnimatorListener(endListener);
                 anim.start(mEndVelocityPxPerMs);
@@ -404,7 +411,7 @@
 
                 AnimatorSet anim = new AnimatorSet();
                 anim.play(mLauncherAlpha.animateToValue(
-                        mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
+                        mLauncherAlpha.value, params.mLauncherAlpha));
                 anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
 
                 anim.setDuration(duration);
@@ -429,13 +436,14 @@
         }
         applyTransformUnchecked();
 
-        setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+        mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
     }
 
     @Override
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+        super.onRecentsAnimationCanceled(thumbnailData);
         mRecentsView.setRecentsAnimationTargets(null, null);
-        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
+        mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 02f4c40..c479250 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -26,7 +26,6 @@
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
-import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
@@ -55,9 +54,9 @@
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.SwipeSharedState;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
@@ -80,10 +79,11 @@
     public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
 
     private final RecentsAnimationDeviceState mDeviceState;
+    private final TaskAnimationManager mTaskAnimationManager;
     private final GestureState mGestureState;
+    private RecentsAnimationCallbacks mActiveCallbacks;
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
-    private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
     private final SysUINavigationMode.Mode mMode;
     private final BaseActivityInterface mActivityInterface;
@@ -95,6 +95,7 @@
     private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
+
     private VelocityTracker mVelocityTracker;
 
     private BaseSwipeUpHandler mInteractionHandler;
@@ -123,16 +124,16 @@
         ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
                 true /* restoreHomeStackPosition */);
     };
-    private int mLogId;
 
     public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, RunningTaskInfo runningTaskInfo,
-            boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
-            SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
-            boolean disableHorizontalSwipe, Factory handlerFactory, int logId) {
+            TaskAnimationManager taskAnimationManager, GestureState gestureState,
+            RunningTaskInfo runningTaskInfo, boolean isDeferredDownTarget,
+            Consumer<OtherActivityInputConsumer> onCompleteCallback,
+            InputMonitorCompat inputMonitorCompat, boolean disableHorizontalSwipe,
+            Factory handlerFactory) {
         super(base);
-        mLogId = logId;
         mDeviceState = deviceState;
+        mTaskAnimationManager = taskAnimationManager;
         mGestureState = gestureState;
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         mRunningTask = runningTaskInfo;
@@ -147,9 +148,8 @@
         mVelocityTracker = VelocityTracker.obtain();
         mInputMonitorCompat = inputMonitorCompat;
 
-        boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
+        boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
-        mSwipeSharedState = swipeSharedState;
 
         mNavBarPosition = new NavBarPosition(base);
         mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop();
@@ -329,25 +329,22 @@
             long touchTimeMs, boolean isLikelyToStartNewTask) {
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
 
-        RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener();
-        final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mGestureState, mRunningTask,
-                touchTimeMs, listenerSet != null, isLikelyToStartNewTask);
+        mInteractionHandler = mHandlerFactory.newHandler(mGestureState, mRunningTask, touchTimeMs,
+                mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask);
+        mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
+        mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
+        mInteractionHandler.initWhenReady();
 
-        mInteractionHandler = handler;
-        handler.setGestureEndCallback(this::onInteractionGestureFinished);
-        mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
-        handler.initWhenReady();
-
-        if (listenerSet != null) {
-            listenerSet.addListener(handler);
-            mSwipeSharedState.applyActiveRecentsAnimationState(handler);
+        if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+            mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
+            mActiveCallbacks.addListener(mInteractionHandler);
+            mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
             notifyGestureStarted();
         } else {
-            RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks();
-            callbacks.addListener(handler);
-            Intent intent = handler.getLaunchIntent();
-            intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
-            startRecentsActivityAsync(intent, callbacks);
+            Intent intent = mInteractionHandler.getLaunchIntent();
+            intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
+            mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
+                    mInteractionHandler);
         }
     }
 
@@ -402,7 +399,7 @@
             // The consumer is being switched while we are active. Set up the shared state to be
             // used by the next animation
             removeListener();
-            mInteractionHandler.onConsumerAboutToBeSwitched(mSwipeSharedState);
+            mInteractionHandler.onConsumerAboutToBeSwitched();
         }
     }
 
@@ -415,9 +412,8 @@
     }
 
     private void removeListener() {
-        RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener();
-        if (listenerSet != null) {
-            listenerSet.removeListener(mInteractionHandler);
+        if (mActiveCallbacks != null) {
+            mActiveCallbacks.removeListener(mInteractionHandler);
         }
     }
 
@@ -432,11 +428,6 @@
     }
 
     @Override
-    public boolean useSharedSwipeState() {
-        return mInteractionHandler != null;
-    }
-
-    @Override
     public boolean allowInterceptByParent() {
         return !mPassedPilferInputSlop;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java
index 97ca730..9826b3a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java
@@ -69,7 +69,8 @@
 
     private final float mSquaredSlop;
 
-    private Context mContext;
+    private final Context mContext;
+    private final GestureState mGestureState;
 
     private RecentsView mRecentsView;
 
@@ -77,6 +78,7 @@
             InputConsumer delegate, InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
         mContext = context;
+        mGestureState = gestureState;
 
         float slop = ViewConfiguration.get(context).getScaledTouchSlop();
         mSquaredSlop = slop * slop;
@@ -90,8 +92,8 @@
         return TYPE_QUICK_CAPTURE | mDelegate.getType();
     }
 
-    private boolean onActivityInit(final BaseDraggingActivity activity, Boolean alreadyOnHome) {
-        mRecentsView = activity.getOverviewPanel();
+    private boolean onActivityInit(Boolean alreadyOnHome) {
+        mRecentsView = mGestureState.getActivityInterface().getCreatedActivity().getOverviewPanel();
 
         return true;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
index e04c0c7..d34b40b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
@@ -18,17 +18,17 @@
 import android.view.MotionEvent;
 
 import com.android.quickstep.InputConsumer;
-import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.TaskAnimationManager;
 
 /**
  * A NO_OP input consumer which also resets any pending gesture
  */
 public class ResetGestureInputConsumer implements InputConsumer {
 
-    private final SwipeSharedState mSwipeSharedState;
+    private final TaskAnimationManager mTaskAnimationManager;
 
-    public ResetGestureInputConsumer(SwipeSharedState swipeSharedState) {
-        mSwipeSharedState = swipeSharedState;
+    public ResetGestureInputConsumer(TaskAnimationManager taskAnimationManager) {
+        mTaskAnimationManager = taskAnimationManager;
     }
 
     @Override
@@ -39,8 +39,8 @@
     @Override
     public void onMotionEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN
-                && mSwipeSharedState.getActiveListener() != null) {
-            mSwipeSharedState.clearAllState(false /* finishAnimation */);
+                && mTaskAnimationManager.isRecentsAnimationRunning()) {
+            mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
         }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
index 9a3bb76..fabfc4b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -33,7 +33,7 @@
      */
     public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID";
 
-    public ActiveGestureLog() {
+    private ActiveGestureLog() {
         super("touch_interaction_log", 40);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 0655c73..5a65c15 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -102,8 +102,9 @@
     @Override
     public void startHome() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            switchToScreenshot(() -> finishRecentsAnimation(true /* toRecents */,
-                    () -> mActivity.getStateManager().goToState(NORMAL)));
+            switchToScreenshot(null,
+                    () -> finishRecentsAnimation(true /* toRecents */,
+                            () -> mActivity.getStateManager().goToState(NORMAL)));
         } else {
             mActivity.getStateManager().goToState(NORMAL);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 6120b9e..5d4665d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -1851,20 +1851,20 @@
         return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
     }
 
-
     /** If it's in the live tile mode, switch the running task into screenshot mode. */
-    public void switchToScreenshot(Runnable onFinishRunnable) {
+    public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
         TaskView taskView = getRunningTaskView();
-        if (taskView == null) {
-            if (onFinishRunnable != null) {
-                onFinishRunnable.run();
+        if (taskView != null) {
+            taskView.setShowScreenshot(true);
+            if (thumbnailData != null) {
+                taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData);
+            } else {
+                taskView.getThumbnail().refresh();
             }
-            return;
+            ViewUtils.postDraw(taskView, onFinishRunnable);
+        } else {
+            onFinishRunnable.run();
         }
-
-        taskView.setShowScreenshot(true);
-        taskView.getThumbnail().refresh();
-        ViewUtils.postDraw(taskView, onFinishRunnable);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 409bec6..cb18001 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -35,8 +35,8 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-import java.util.function.BiPredicate;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
@@ -44,21 +44,26 @@
 @TargetApi(Build.VERSION_CODES.P)
 public interface BaseActivityInterface<T extends BaseDraggingActivity> {
 
-    void onTransitionCancelled(T activity, boolean activityVisible);
+    void onTransitionCancelled(boolean activityVisible);
 
     int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect);
 
-    void onSwipeUpToRecentsComplete(T activity);
+    void onSwipeUpToRecentsComplete();
 
-    default void onSwipeUpToHomeComplete(T activity) { }
+    default void onSwipeUpToHomeComplete() { }
     void onAssistantVisibilityChanged(float visibility);
 
-    @NonNull HomeAnimationFactory prepareHomeUI(T activity);
+    @NonNull HomeAnimationFactory prepareHomeUI();
 
-    AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
-            boolean animateActivity, Consumer<AnimatorPlaybackController> callback);
+    AnimationFactory prepareRecentsUI(boolean activityVisible, boolean animateActivity,
+            Consumer<AnimatorPlaybackController> callback);
 
-    ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
+    ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener);
+
+    /**
+     * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
+     */
+    default void setOnDeferredActivityLaunchCallback(Runnable r) {}
 
     @Nullable
     T getCreatedActivity();
@@ -90,13 +95,14 @@
 
     boolean isInLiveTileMode();
 
-    void onLaunchTaskFailed(T activity);
+    void onLaunchTaskFailed();
 
-    void onLaunchTaskSuccess(T activity);
+    void onLaunchTaskSuccess();
 
     default void closeOverlay() { }
 
-    default void switchToScreenshot(ThumbnailData thumbnailData, Runnable runnable) {}
+    default void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData,
+            Runnable runnable) {}
 
     interface AnimationFactory {
 
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index de64227..98ff410 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -15,22 +15,215 @@
  */
 package com.android.quickstep;
 
+import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import java.util.ArrayList;
 
 /**
  * Manages the state for an active system gesture, listens for events from the system and Launcher,
  * and fires events when the states change.
  */
-public class GestureState {
+public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener {
 
-    // Needed to interact with the current activity
-    private BaseActivityInterface mActivityInterface;
+    /**
+     * Defines the end targets of a gesture and the associated state.
+     */
+    public enum GestureEndTarget {
+        HOME(true, ContainerType.WORKSPACE, false),
 
-    public GestureState(BaseActivityInterface activityInterface) {
-        mActivityInterface = activityInterface;
+        RECENTS(true, ContainerType.TASKSWITCHER, true),
+
+        NEW_TASK(false, ContainerType.APP, true),
+
+        LAST_TASK(false, ContainerType.APP, false);
+
+        GestureEndTarget(boolean isLauncher, int containerType,
+                boolean recentsAttachedToAppWindow) {
+            this.isLauncher = isLauncher;
+            this.containerType = containerType;
+            this.recentsAttachedToAppWindow = recentsAttachedToAppWindow;
+        }
+
+        /** Whether the target is in the launcher activity. Implicitly, if the end target is going
+         to Launcher, then we can not interrupt the animation to start another gesture. */
+        public final boolean isLauncher;
+        /** Used to log where the user ended up after the gesture ends */
+        public final int containerType;
+        /** Whether RecentsView should be attached to the window as we animate to this target */
+        public final boolean recentsAttachedToAppWindow;
     }
 
+    private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
+    private static int FLAG_COUNT = 0;
+    private static int getFlagForIndex(String name) {
+        if (DEBUG_STATES) {
+            STATE_NAMES.add(name);
+        }
+        int index = 1 << FLAG_COUNT;
+        FLAG_COUNT++;
+        return index;
+    }
+
+    // Called when the end target as been set
+    public static final int STATE_END_TARGET_SET =
+            getFlagForIndex("STATE_END_TARGET_SET");
+
+    // Called when the end target animation has finished
+    public static final int STATE_END_TARGET_ANIMATION_FINISHED =
+            getFlagForIndex("STATE_END_TARGET_ANIMATION_FINISHED");
+
+    // Called when the recents animation has been requested to start
+    public static final int STATE_RECENTS_ANIMATION_INITIALIZED =
+            getFlagForIndex("STATE_RECENTS_ANIMATION_INITIALIZED");
+
+    // Called when the recents animation is started and the TaskAnimationManager has been updated
+    // with the controller and targets
+    public static final int STATE_RECENTS_ANIMATION_STARTED =
+            getFlagForIndex("STATE_RECENTS_ANIMATION_STARTED");
+
+    // Called when the recents animation is canceled
+    public static final int STATE_RECENTS_ANIMATION_CANCELED =
+            getFlagForIndex("STATE_RECENTS_ANIMATION_CANCELED");
+
+    // Called when the recents animation finishes
+    public static final int STATE_RECENTS_ANIMATION_FINISHED =
+            getFlagForIndex("STATE_RECENTS_ANIMATION_FINISHED");
+
+    // Always called when the recents animation ends (regardless of cancel or finish)
+    public static final int STATE_RECENTS_ANIMATION_ENDED =
+            getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED");
+
+
+    // Needed to interact with the current activity
+    private final BaseActivityInterface mActivityInterface;
+    private final MultiStateCallback mStateCallback;
+    private final int mGestureId;
+
+    private GestureEndTarget mEndTarget;
+    // TODO: This can be removed once we stop finishing the animation when starting a new task
+    private int mFinishingRecentsAnimationTaskId = -1;
+
+    public GestureState(BaseActivityInterface activityInterface, int gestureId) {
+        mActivityInterface = activityInterface;
+        mGestureId = gestureId;
+        mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+    }
+
+    public GestureState() {
+        // Do nothing, only used for initializing the gesture state prior to user unlock
+        mActivityInterface = null;
+        mGestureId = -1;
+        mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+    }
+
+    /**
+     * Sets the given {@param stateFlag}s.
+     */
+    public void setState(int stateFlag) {
+        mStateCallback.setState(stateFlag);
+    }
+
+    /**
+     * Adds a callback for when the states matching the given {@param stateMask} is set.
+     */
+    public void runOnceAtState(int stateMask, Runnable callback) {
+        mStateCallback.runOnceAtState(stateMask, callback);
+    }
+
+    /**
+     * @return the interface to the activity handing the UI updates for this gesture.
+     */
     public <T extends BaseDraggingActivity> BaseActivityInterface<T> getActivityInterface() {
         return mActivityInterface;
     }
+
+    /**
+     * @return the id for this particular gesture.
+     */
+    public int getGestureId() {
+        return mGestureId;
+    }
+
+    /**
+     * @return the end target for this gesture (if known).
+     */
+    public GestureEndTarget getEndTarget() {
+        return mEndTarget;
+    }
+
+    /**
+     * @return whether the current gesture is still running a recents animation to a state in the
+     *         Launcher or Recents activity.
+     */
+    public boolean isRunningAnimationToLauncher() {
+        return isRecentsAnimationRunning() && mEndTarget != null && mEndTarget.isLauncher;
+    }
+
+    /**
+     * Sets the end target of this gesture and immediately notifies the state changes.
+     */
+    public void setEndTarget(GestureEndTarget target) {
+        setEndTarget(target, true /* isAtomic */);
+    }
+
+    /**
+     * Sets the end target of this gesture, but if {@param isAtomic} is {@code false}, then the
+     * caller must explicitly set {@link #STATE_END_TARGET_ANIMATION_FINISHED} themselves.
+     */
+    public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
+        mEndTarget = target;
+        mStateCallback.setState(STATE_END_TARGET_SET);
+        if (isAtomic) {
+            mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+        }
+    }
+
+    /**
+     * @return the id for the task that was about to be launched following the finish of the recents
+     * animation.  Only defined between when the finish-recents call was made and the launch
+     * activity call is made.
+     */
+    public int getFinishingRecentsAnimationTaskId() {
+        return mFinishingRecentsAnimationTaskId;
+    }
+
+    /**
+     * Sets the id for the task will be launched after the recents animation is finished. Once the
+     * animation has finished then the id will be reset to -1.
+     */
+    public void setFinishingRecentsAnimationTaskId(int taskId) {
+        mFinishingRecentsAnimationTaskId = taskId;
+        mStateCallback.runOnceAtState(STATE_RECENTS_ANIMATION_FINISHED, () -> {
+            mFinishingRecentsAnimationTaskId = -1;
+        });
+    }
+
+    /**
+     * @return whether the recents animation is started but not yet ended
+     */
+    public boolean isRecentsAnimationRunning() {
+        return mStateCallback.hasStates(STATE_RECENTS_ANIMATION_INITIALIZED) &&
+                !mStateCallback.hasStates(STATE_RECENTS_ANIMATION_ENDED);
+    }
+
+    @Override
+    public void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED);
+    }
+
+    @Override
+    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+        mStateCallback.setState(STATE_RECENTS_ANIMATION_CANCELED);
+        mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
+    }
+
+    @Override
+    public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+        mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED);
+        mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 62c0ded..918645d 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -52,10 +52,6 @@
 
     int getType();
 
-    default boolean useSharedSwipeState() {
-        return false;
-    }
-
     /**
      * Returns true if the user has crossed the threshold for it to be an explicit action.
      */
@@ -65,6 +61,8 @@
 
     /**
      * Called by the event queue when the consumer is about to be switched to a new consumer.
+     * Consumers should update the state accordingly here before the state is passed to the new
+     * consumer.
      */
     default void onConsumerAboutToBeSwitched() { }
 
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
new file mode 100644
index 0000000..6c65e01
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 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 com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.os.Looper;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.StringJoiner;
+import java.util.function.Consumer;
+
+/**
+ * Utility class to help manage multiple callbacks based on different states.
+ */
+public class MultiStateCallback {
+
+    private static final String TAG = "MultiStateCallback";
+    public static final boolean DEBUG_STATES = false;
+
+    private final SparseArray<LinkedList<Runnable>> mCallbacks = new SparseArray<>();
+    private final SparseArray<ArrayList<Consumer<Boolean>>> mStateChangeListeners =
+            new SparseArray<>();
+
+    private final String[] mStateNames;
+
+    private int mState = 0;
+
+    public MultiStateCallback(String[] stateNames) {
+        mStateNames = DEBUG_STATES ? stateNames : null;
+    }
+
+    /**
+     * Adds the provided state flags to the global state on the UI thread and executes any callbacks
+     * as a result.
+     */
+    public void setStateOnUiThread(int stateFlag) {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            setState(stateFlag);
+        } else {
+            postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> setState(stateFlag));
+        }
+    }
+
+    /**
+     * Adds the provided state flags to the global state and executes any callbacks as a result.
+     */
+    public void setState(int stateFlag) {
+        if (DEBUG_STATES) {
+            Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
+                    + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
+        }
+
+        final int oldState = mState;
+        mState = mState | stateFlag;
+
+        int count = mCallbacks.size();
+        for (int i = 0; i < count; i++) {
+            int state = mCallbacks.keyAt(i);
+
+            if ((mState & state) == state) {
+                LinkedList<Runnable> callbacks = mCallbacks.valueAt(i);
+                while (!callbacks.isEmpty()) {
+                    callbacks.pollFirst().run();
+                }
+            }
+        }
+        notifyStateChangeListeners(oldState);
+    }
+
+    /**
+     * Adds the provided state flags to the global state and executes any change handlers
+     * as a result.
+     */
+    public void clearState(int stateFlag) {
+        if (DEBUG_STATES) {
+            Log.d(TAG, "[" + System.identityHashCode(this) + "] Removing "
+                    + convertToFlagNames(stateFlag) + " from " + convertToFlagNames(mState));
+        }
+
+        int oldState = mState;
+        mState = mState & ~stateFlag;
+        notifyStateChangeListeners(oldState);
+    }
+
+    private void notifyStateChangeListeners(int oldState) {
+        int count = mStateChangeListeners.size();
+        for (int i = 0; i < count; i++) {
+            int state = mStateChangeListeners.keyAt(i);
+            boolean wasOn = (state & oldState) == state;
+            boolean isOn = (state & mState) == state;
+
+            if (wasOn != isOn) {
+                ArrayList<Consumer<Boolean>> listeners = mStateChangeListeners.valueAt(i);
+                for (Consumer<Boolean> listener : listeners) {
+                    listener.accept(isOn);
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets a callback to be run when the provided states in the given {@param stateMask} is
+     * enabled. The callback is only run *once*, and if the states are already set at the time of
+     * this call then the callback will be made immediately.
+     */
+    public void runOnceAtState(int stateMask, Runnable callback) {
+        if ((mState & stateMask) == stateMask) {
+            callback.run();
+        } else {
+            final LinkedList<Runnable> callbacks;
+            if (mCallbacks.indexOfKey(stateMask) >= 0) {
+                callbacks = mCallbacks.get(stateMask);
+                if (FeatureFlags.IS_DOGFOOD_BUILD && callbacks.contains(callback)) {
+                    throw new IllegalStateException("Existing callback for state found");
+                }
+            } else {
+                callbacks = new LinkedList<>();
+                mCallbacks.put(stateMask, callbacks);
+            }
+            callbacks.add(callback);
+        }
+    }
+
+    /**
+     * Adds a persistent listener to be called states in the given {@param stateMask} are enabled
+     * or disabled.
+     */
+    public void addChangeListener(int stateMask, Consumer<Boolean> listener) {
+        final ArrayList<Consumer<Boolean>> listeners;
+        if (mStateChangeListeners.indexOfKey(stateMask) >= 0) {
+            listeners = mStateChangeListeners.get(stateMask);
+        } else {
+            listeners = new ArrayList<>();
+            mStateChangeListeners.put(stateMask, listeners);
+        }
+        listeners.add(listener);
+    }
+
+    public int getState() {
+        return mState;
+    }
+
+    public boolean hasStates(int stateMask) {
+        return (mState & stateMask) == stateMask;
+    }
+
+    private String convertToFlagNames(int flags) {
+        StringJoiner joiner = new StringJoiner(", ", "[", " (" + flags + ")]");
+        for (int i = 0; i < mStateNames.length; i++) {
+            if ((flags & (1 << i)) != 0) {
+                joiner.add(mStateNames[i]);
+            }
+        }
+        return joiner.toString();
+    }
+
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 2918879..acf61b4 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -127,7 +127,7 @@
      */
     public interface RecentsAnimationListener {
         default void onRecentsAnimationStart(RecentsAnimationController controller,
-                RecentsAnimationTargets targetSet) {}
+                RecentsAnimationTargets targets) {}
 
         /**
          * Callback from the system when the recents animation is canceled. {@param thumbnailData}
@@ -135,6 +135,9 @@
          */
         default void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {}
 
+        /**
+         * Callback made whenever the recents animation is finished.
+         */
         default void onRecentsAnimationFinished(RecentsAnimationController controller) {}
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index d938dc5..9d5120d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -71,7 +71,7 @@
      * currently being animated.
      */
     public ThumbnailData screenshotTask(int taskId) {
-        return mController != null ? mController.screenshotTask(taskId) : null;
+        return mController.screenshotTask(taskId);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index 9353759..718c5ba 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -41,13 +41,4 @@
     public boolean hasTargets() {
         return unfilteredApps.length != 0;
     }
-
-    /**
-     * Clones the target set without any actual targets. Used only when continuing a gesture after
-     * the actual recents animation has finished.
-     */
-    public RecentsAnimationTargets cloneWithoutTargets() {
-        return new RecentsAnimationTargets(new RemoteAnimationTargetCompat[0],
-                new RemoteAnimationTargetCompat[0], homeContentInsets, minimizedHomeBounds);
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
new file mode 100644
index 0000000..6873899
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -0,0 +1,175 @@
+/*
+ * 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;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
+
+import android.content.Intent;
+import android.util.Log;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
+
+    private RecentsAnimationController mController;
+    private RecentsAnimationCallbacks mCallbacks;
+    private RecentsAnimationTargets mTargets;
+    // Temporary until we can hook into gesture state events
+    private GestureState mLastGestureState;
+    private ThumbnailData mCanceledThumbnail;
+
+    /**
+     * Preloads the recents animation.
+     */
+    public void preloadRecentsAnimation(Intent intent) {
+        // Pass null animation handler to indicate this start is for preloading
+        UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+                .startRecentsActivity(intent, null, null, null, null));
+    }
+
+    /**
+     * Starts a new recents animation for the activity with the given {@param intent}.
+     */
+    @UiThread
+    public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
+            Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
+        // Notify if recents animation is still running
+        if (mController != null) {
+            String msg = "New recents animation started before old animation completed";
+            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                throw new IllegalArgumentException(msg);
+            } else {
+                Log.e("TaskAnimationManager", msg, new Exception());
+            }
+        }
+        // But force-finish it anyways
+        finishRunningRecentsAnimation(false /* toHome */);
+
+        final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
+        mLastGestureState = gestureState;
+        mCallbacks = new RecentsAnimationCallbacks(activityInterface.shouldMinimizeSplitScreen());
+        mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
+            @Override
+            public void onRecentsAnimationStart(RecentsAnimationController controller,
+                    RecentsAnimationTargets targets) {
+                mController = controller;
+                mTargets = targets;
+            }
+
+            @Override
+            public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+                if (thumbnailData != null) {
+                    // If a screenshot is provided, switch to the screenshot before cleaning up
+                    activityInterface.switchRunningTaskViewToScreenshot(thumbnailData,
+                            () -> cleanUpRecentsAnimation());
+                } else {
+                    cleanUpRecentsAnimation();
+                }
+            }
+
+            @Override
+            public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+                cleanUpRecentsAnimation();
+            }
+        });
+        mCallbacks.addListener(gestureState);
+        mCallbacks.addListener(listener);
+        UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+                .startRecentsActivity(intent, null, mCallbacks, null, null));
+        gestureState.setState(STATE_RECENTS_ANIMATION_INITIALIZED);
+        return mCallbacks;
+    }
+
+    /**
+     * Continues the existing running recents animation for a new gesture.
+     */
+    public RecentsAnimationCallbacks continueRecentsAnimation(GestureState gestureState) {
+        mCallbacks.removeListener(mLastGestureState);
+        mLastGestureState = gestureState;
+        mCallbacks.addListener(gestureState);
+        return mCallbacks;
+    }
+
+    /**
+     * Finishes the running recents animation.
+     */
+    public void finishRunningRecentsAnimation(boolean toHome) {
+        if (mController != null) {
+            mCallbacks.notifyAnimationCanceled();
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
+                    ? mController::finishAnimationToHome
+                    : mController::finishAnimationToApp);
+            cleanUpRecentsAnimation();
+        }
+    }
+
+    /**
+     * Used to notify a listener of the current recents animation state (used if the listener was
+     * not yet added to the callbacks at the point that the listener callbacks would have been
+     * made).
+     */
+    public void notifyRecentsAnimationState(
+            RecentsAnimationCallbacks.RecentsAnimationListener listener) {
+        if (isRecentsAnimationRunning()) {
+            listener.onRecentsAnimationStart(mController, mTargets);
+        }
+        // TODO: Do we actually need to report canceled/finished?
+    }
+
+    /**
+     * @return whether there is a recents animation running.
+     */
+    public boolean isRecentsAnimationRunning() {
+        return mController != null;
+    }
+
+    /**
+     * Cleans up the recents animation entirely.
+     */
+    private void cleanUpRecentsAnimation() {
+        // Clean up the screenshot if necessary
+        if (mController != null && mCanceledThumbnail != null) {
+            mController.cleanupScreenshot();
+        }
+
+        // Release all the target leashes
+        if (mTargets != null) {
+            mTargets.release();
+        }
+
+        // Remove gesture state from callbacks
+        if (mCallbacks != null && mLastGestureState != null) {
+            mCallbacks.removeListener(mLastGestureState);
+        }
+
+        mController = null;
+        mCallbacks = null;
+        mTargets = null;
+        mCanceledThumbnail = null;
+        mLastGestureState = null;
+    }
+
+    public void dump() {
+        // TODO
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index aa5fce1..b17ed4c 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -130,6 +130,10 @@
     @NavigationModeSwitch
     @Test
     public void goToOverviewFromHome() {
+        // b/142828227
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+            return;
+        }
         mDevice.pressHome();
         assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
                 mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
@@ -140,6 +144,10 @@
     @NavigationModeSwitch
     @Test
     public void goToOverviewFromApp() {
+        // b/142828227
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+            return;
+        }
         startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
 
         mLauncher.getBackground().switchToOverview();
@@ -174,6 +182,10 @@
     @NavigationModeSwitch
     @Test
     public void testOverview() {
+        // b/142828227
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+            return;
+        }
         startAppFastAndWaitForRecentTask(getAppPackageName());
         startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         startTestActivity(2);
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 1d920f9..25224b0 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -25,6 +25,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.util.RaceConditionReproducer;
 import com.android.quickstep.NavigationModeSwitchRule.Mode;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
@@ -91,6 +92,10 @@
     @Test
     @NavigationModeSwitch
     public void testStressSwipeToOverview() {
+        // b/142828227
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+            return;
+        }
         for (int i = 0; i < STRESS_REPEAT_COUNT; ++i) {
             // Destroy Launcher activity.
             closeLauncherActivity();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4b4d793..67c1a04 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -267,6 +267,10 @@
 
     private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>();
 
+    // Used to notify when an activity launch has been deferred because launcher is not yet resumed
+    // TODO: See if we can remove this later
+    private Runnable mOnDeferredActivityLaunchCallback;
+
     private ViewOnDrawExecutor mPendingExecutor;
 
     private LauncherModel mModel;
@@ -1886,7 +1890,10 @@
             // recents animation into launcher. Defer launching the activity until Launcher is
             // next resumed.
             addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
-            UiFactory.clearSwipeSharedState(this, true /* finishAnimation */);
+            if (mOnDeferredActivityLaunchCallback != null) {
+                mOnDeferredActivityLaunchCallback.run();
+                mOnDeferredActivityLaunchCallback = null;
+            }
             return true;
         }
 
@@ -1948,6 +1955,14 @@
     }
 
     /**
+     * Persistant callback which notifies when an activity launch is deferred because the activity
+     * was not yet resumed.
+     */
+    public void setOnDeferredActivityLaunchCallback(Runnable callback) {
+        mOnDeferredActivityLaunchCallback = callback;
+    }
+
+    /**
      * Implementation of the method from LauncherModel.Callbacks.
      */
     @Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
index 6d9ed88..606c990 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java
@@ -96,9 +96,6 @@
 
     public static void resetPendingActivityResults(Launcher launcher, int requestCode) { }
 
-    /** No-op. */
-    public static void clearSwipeSharedState(Launcher launcher, boolean finishAnimation) { }
-
     public static Person[] getPersons(ShortcutInfo si) {
         return Utilities.EMPTY_PERSON_ARRAY;
     }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 5e87612..465cee2 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -34,6 +34,7 @@
 import com.android.launcher3.tapl.AppIcon;
 import com.android.launcher3.tapl.AppIconMenu;
 import com.android.launcher3.tapl.AppIconMenuItem;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
 import com.android.launcher3.views.OptionsPopupView;
@@ -172,6 +173,10 @@
 
     @Test
     public void testWorkspace() throws Exception {
+        // b/142828227
+        if (android.os.Build.MODEL.contains("Cuttlefish") && TestHelpers.isInLauncherProcess()) {
+            return;
+        }
         final Workspace workspace = mLauncher.getWorkspace();
 
         // Test that ensureWorkspaceIsScrollable adds a page by dragging an icon there.