Merge "Using SpringAnimationBuilder instead of SpringObjectAnimator in StaggeredWorkspaceAnim" into ub-launcher3-master
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index b1a3881..6b50088 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherAnimationRunner;
 import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargets;
 import com.android.quickstep.views.IconRecentsView;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -47,12 +46,12 @@
         RemoteAnimationProvider {
     private static final String TAG = "AppToOverviewAnimationProvider";
 
-    private final ActivityControlHelper<T> mHelper;
+    private final BaseActivityInterface<T> mHelper;
     private final int mTargetTaskId;
     private IconRecentsView mRecentsView;
     private AppToOverviewAnimationListener mAnimationReadyListener;
 
-    AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
+    AppToOverviewAnimationProvider(BaseActivityInterface<T> helper, int targetTaskId) {
         mHelper = helper;
         mTargetTaskId = targetTaskId;
     }
@@ -76,7 +75,7 @@
         if (mAnimationReadyListener != null) {
             mAnimationReadyListener.onActivityReady(activity);
         }
-        ActivityControlHelper.AnimationFactory factory =
+        BaseActivityInterface.AnimationFactory factory =
                 mHelper.prepareRecentsUI(activity, wasVisible,
                         false /* animate activity */, (controller) -> {
                             controller.dispatchOnStart();
@@ -86,7 +85,7 @@
                             anim.start();
                         });
         factory.onRemoteAnimationReceived(null);
-        factory.createActivityController(getRecentsLaunchDuration());
+        factory.createActivityInterface(getRecentsLaunchDuration());
         mRecentsView = activity.getOverviewPanel();
         return false;
     }
@@ -113,9 +112,9 @@
             return anim;
         }
 
-        RemoteAnimationTargets targetSet =
+        RemoteAnimationTargets targets =
                 new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_CLOSING);
-        mRecentsView.setTransitionedFromApp(!targetSet.isAnimatingHome());
+        mRecentsView.setTransitionedFromApp(!targets.isAnimatingHome());
 
         RemoteAnimationTargetCompat recentsTarget = null;
         RemoteAnimationTargetCompat closingAppTarget = null;
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
similarity index 89%
rename from go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
rename to go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 37c2c04..2af8441 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -27,21 +27,20 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationTargets;
 import com.android.quickstep.views.IconRecentsView;
 
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
 /**
- * {@link ActivityControlHelper} for recents when the default launcher is different than the
+ * {@link BaseActivityInterface} for recents when the default launcher is different than the
  * currently running one and apps should interact with the {@link RecentsActivity} as opposed
  * to the in-launcher one.
  */
-public final class FallbackActivityControllerHelper extends
-        GoActivityControlHelper<RecentsActivity> {
+public final class FallbackActivityInterface extends
+        GoActivityInterface<RecentsActivity> {
 
-    public FallbackActivityControllerHelper() { }
+    public FallbackActivityInterface() { }
 
     @Override
     public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
@@ -64,12 +63,12 @@
                 if (!isAnimatingToRecents) {
                     rv.setAlpha(1);
                 }
-                createActivityController(getSwipeUpDestinationAndLength(
+                createActivityInterface(getSwipeUpDestinationAndLength(
                         activity.getDeviceProfile(), activity, new Rect()));
             }
 
             @Override
-            public void createActivityController(long transitionLength) {
+            public void createActivityInterface(long transitionLength) {
                 if (!isAnimatingToRecents) {
                     return;
                 }
diff --git a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java
similarity index 93%
rename from go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
rename to go/quickstep/src/com/android/quickstep/GoActivityInterface.java
index 274a347..5ce0f4c 100644
--- a/go/quickstep/src/com/android/quickstep/GoActivityControlHelper.java
+++ b/go/quickstep/src/com/android/quickstep/GoActivityInterface.java
@@ -13,8 +13,8 @@
  *
  * @param <T> activity that contains the overview
  */
-public abstract class GoActivityControlHelper<T extends BaseDraggingActivity> implements
-        ActivityControlHelper<T> {
+public abstract class GoActivityInterface<T extends BaseDraggingActivity> implements
+        BaseActivityInterface<T> {
 
     @Override
     public void onTransitionCancelled(T activity, boolean activityVisible) {
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
similarity index 93%
rename from go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
rename to go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 8993b67..5bff8e8 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -30,10 +30,10 @@
 import java.util.function.Consumer;
 
 /**
- * {@link ActivityControlHelper} for the in-launcher recents.
+ * {@link BaseActivityInterface} for the in-launcher recents.
  * TODO: Implement the app to overview animation functionality
  */
-public final class LauncherActivityControllerHelper extends GoActivityControlHelper<Launcher> {
+public final class LauncherActivityInterface extends GoActivityInterface<Launcher> {
 
     @Override
     public AnimationFactory prepareRecentsUI(Launcher activity,
@@ -43,8 +43,7 @@
         activity.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
         //TODO: Implement this based off where the recents view needs to be for app => recents anim.
         return new AnimationFactory() {
-            @Override
-            public void createActivityController(long transitionLength) {
+            public void createActivityInterface(long transitionLength) {
                 callback.accept(activity.getStateManager().createAnimationToNewWorkspace(
                         fromState, OVERVIEW, transitionLength));
             }
diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 9c409cc..a436ce7 100644
--- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -40,26 +40,28 @@
 public class OverviewCommandHelper {
 
     private final Context mContext;
-    private final ActivityManagerWrapper mAM;
+    private final RecentsAnimationDeviceState mDeviceState;
     private final RecentsModel mRecentsModel;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
 
-    public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
+    public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
+            OverviewComponentObserver observer) {
         mContext = context;
-        mAM = ActivityManagerWrapper.getInstance();
+        mDeviceState = deviceState;
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
 
     public void onOverviewToggle() {
         // If currently screen pinning, do not enter overview
-        if (mAM.isScreenPinningActive()) {
+        if (mDeviceState.isScreenPinningActive()) {
             return;
         }
 
-        mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        ActivityManagerWrapper.getInstance()
+                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
         MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
@@ -99,7 +101,7 @@
 
     private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
 
-        protected final ActivityControlHelper<T> mHelper;
+        protected final BaseActivityInterface<T> mHelper;
         private final long mCreateTime;
 
         private final long mToggleClickedTime = SystemClock.uptimeMillis();
@@ -107,7 +109,7 @@
         private ActivityInitListener<T> mListener;
 
         public RecentsActivityCommand() {
-            mHelper = mOverviewComponentObserver.getActivityControlHelper();
+            mHelper = mOverviewComponentObserver.getActivityInterface();
             mCreateTime = SystemClock.elapsedRealtime();
 
             // Preload the plan
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 6b936cb..f743663 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
@@ -56,7 +57,7 @@
         public void onInitialize(Bundle bundle) throws RemoteException {
             ISystemUiProxy iSystemUiProxy = ISystemUiProxy.Stub
                     .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
-            mRecentsModel.setSystemUiProxy(iSystemUiProxy);
+            SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(iSystemUiProxy);
         }
 
         @Override
@@ -120,7 +121,7 @@
         public void onMotionEvent(MotionEvent ev) { }
 
         public void onBind(ISystemUiProxy iSystemUiProxy) {
-            mRecentsModel.setSystemUiProxy(iSystemUiProxy);
+            SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(iSystemUiProxy);
         }
     };
 
@@ -147,7 +148,7 @@
     public void onUserUnlocked() {
         mRecentsModel = RecentsModel.INSTANCE.get(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
-        mOverviewCommandHelper = new OverviewCommandHelper(this,
+        mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
                 mOverviewComponentObserver);
     }
 
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 2f8af44..ff73679 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
@@ -23,8 +23,6 @@
 
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.Log;
 import android.view.Gravity;
 
 import com.android.launcher3.DeviceProfile;
@@ -45,13 +43,11 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.TouchInteractionService;
-import com.android.quickstep.util.SharedApiCompat;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 
 import java.util.ArrayList;
 
@@ -60,21 +56,16 @@
  */
 public abstract class RecentsUiFactory {
 
-    public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
-
     private static final String TAG = RecentsUiFactory.class.getSimpleName();
 
-    private static AsyncCommand newSetShelfHeightCmd(Context context) {
-        return (visible, height) -> {
-            ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(context).getSystemUiProxy();
-            if (sysUiProxy == null) return;
-            try {
-                SharedApiCompat.setShelfHeight(sysUiProxy, visible != 0, height);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error setShelfHeight", e);
-            }
-        };
-    }
+    public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
+
+    /**
+     * Reusable command for applying the shelf height on the background thread.
+     */
+    public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> {
+        SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
+    };
 
     public static RotationMode ROTATION_LANDSCAPE = new RotationMode(-90) {
         @Override
@@ -218,9 +209,8 @@
         DeviceProfile profile = launcher.getDeviceProfile();
         boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
                 && !profile.isVerticalBarLayout();
-        UiThreadHelper.runAsyncCommand(launcher, newSetShelfHeightCmd(launcher),
-                visible ? 1 : 0, profile.hotseatBarSizePx);
-
+        UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT, visible ? 1 : 0,
+                profile.hotseatBarSizePx);
         if (state == NORMAL) {
             launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index e215cfe..ee2e951 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -50,7 +50,7 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.views.RecentsView;
 
@@ -120,7 +120,7 @@
      * having it as part of the existing animation to the target state.
      */
     private boolean handlingOverviewAnim() {
-        int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
         return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 6576ec5..5c3b55d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -45,9 +45,9 @@
 import com.android.launcher3.touch.SwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 
@@ -82,7 +82,7 @@
 
     @Override
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags();
+        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
         if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) {
             return NORMAL;
         }
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 1b49732..8a11ac8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargets;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -49,14 +48,14 @@
     private static final long RECENTS_LAUNCH_DURATION = 250;
     private static final String TAG = "AppToOverviewAnimationProvider";
 
-    private final ActivityControlHelper<T> mHelper;
+    private final BaseActivityInterface<T> mHelper;
     // The id of the currently running task that is transitioning to overview.
     private final int mTargetTaskId;
 
     private T mActivity;
     private RecentsView mRecentsView;
 
-    AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
+    AppToOverviewAnimationProvider(BaseActivityInterface<T> helper, int targetTaskId) {
         mHelper = helper;
         mTargetTaskId = targetTaskId;
     }
@@ -70,7 +69,7 @@
     boolean onActivityReady(T activity, Boolean wasVisible) {
         activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
         AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
-        ActivityControlHelper.AnimationFactory factory =
+        BaseActivityInterface.AnimationFactory factory =
                 mHelper.prepareRecentsUI(activity, wasVisible,
                 false /* animate activity */, (controller) -> {
                     controller.dispatchOnStart();
@@ -80,7 +79,7 @@
                     anim.start();
                 });
         factory.onRemoteAnimationReceived(null);
-        factory.createActivityController(RECENTS_LAUNCH_DURATION);
+        factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
         factory.setRecentsAttachedToAppWindow(true, false);
         mActivity = activity;
         mRecentsView = mActivity.getOverviewPanel();
@@ -115,11 +114,11 @@
             return anim;
         }
 
-        RemoteAnimationTargets targetSet = new RemoteAnimationTargets(appTargets,
+        RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets,
                 wallpaperTargets, MODE_CLOSING);
 
         // Use the top closing app to determine the insets for the animation
-        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mTargetTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
         if (runningTaskTarget == null) {
             Log.e(TAG, "No closing app");
             anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION));
@@ -149,12 +148,12 @@
         valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
         valueAnimator.addUpdateListener((v) -> {
             params.setProgress((float) v.getAnimatedValue())
-                    .setTargetSet(targetSet)
+                    .setTargetSet(targets)
                     .setLauncherOnTop(true);
             clipHelper.applyTransform(params);
         });
 
-        if (targetSet.isAnimatingHome()) {
+        if (targets.isAnimatingHome()) {
             // If we are animating home, fade in the opening targets
             RemoteAnimationTargets openingSet = new RemoteAnimationTargets(appTargets,
                     wallpaperTargets, MODE_OPENING);
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 5d7d2ff..e1e994c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -56,23 +56,22 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.views.FloatingIconView;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
 import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.RemoteAnimationTargets;
-import com.android.quickstep.util.RecentsAnimationTargets;
-import com.android.quickstep.util.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 
+import java.util.ArrayList;
 import java.util.function.Consumer;
 
 /**
@@ -98,7 +97,7 @@
 
     protected final Context mContext;
     protected final OverviewComponentObserver mOverviewComponentObserver;
-    protected final ActivityControlHelper<T> mActivityControlHelper;
+    protected final BaseActivityInterface<T> mActivityInterface;
     protected final RecentsModel mRecentsModel;
     protected final int mRunningTaskId;
 
@@ -115,7 +114,13 @@
     protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
 
     protected final ActivityInitListener mActivityInitListener;
-    protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
+    protected final InputConsumerController mInputConsumer;
+
+    protected RecentsAnimationController mRecentsAnimationController;
+    protected RecentsAnimationTargets mRecentsAnimationTargets;
+
+    // Callbacks to be made once the recents animation starts
+    private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
 
     protected T mActivity;
     protected Q mRecentsView;
@@ -130,18 +135,17 @@
     protected boolean mCanceled;
     protected int mFinishingRecentsAnimationForNewTaskId = -1;
 
-    protected BaseSwipeUpHandler(Context context,
+    protected BaseSwipeUpHandler(Context context, GestureState gestureState,
             OverviewComponentObserver overviewComponentObserver,
             RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) {
         mContext = context;
         mOverviewComponentObserver = overviewComponentObserver;
-        mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
+        mActivityInterface = gestureState.getActivityInterface();
         mRecentsModel = recentsModel;
         mActivityInitListener =
-                mActivityControlHelper.createActivityInitListener(this::onActivityInit);
+                mActivityInterface.createActivityInitListener(this::onActivityInit);
         mRunningTaskId = runningTaskId;
-        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
-                this::createNewInputProxyHandler);
+        mInputConsumer = inputConsumer;
         mMode = SysUINavigationMode.getMode(context);
 
         mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
@@ -210,8 +214,8 @@
     protected void linkRecentsViewScroll() {
         SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
             mTransformParams.setSyncTransactionApplier(applier);
-            mRecentsAnimationWrapper.runOnInit(() ->
-                    mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
+            runOnRecentsAnimationStart(() ->
+                    mRecentsAnimationTargets.addDependentTransactionApplier(applier));
         });
 
         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
@@ -219,8 +223,10 @@
                 updateFinalShift();
             }
         });
-        mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
         mRecentsView.setAppWindowAnimationHelper(mAppWindowAnimationHelper);
+        runOnRecentsAnimationStart(() ->
+                mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
+                        mRecentsAnimationTargets));
     }
 
     protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
@@ -232,7 +238,7 @@
         } else {
             int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
             mFinishingRecentsAnimationForNewTaskId = taskId;
-            mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
+            mRecentsAnimationController.finish(true /* toRecents */, () -> {
                 if (!mCanceled) {
                     TaskView nextTask = mRecentsView.getTaskView(taskId);
                     if (nextTask != null) {
@@ -240,10 +246,10 @@
                                 success -> {
                                     resultCallback.accept(success);
                                     if (!success) {
-                                        mActivityControlHelper.onLaunchTaskFailed(mActivity);
+                                        mActivityInterface.onLaunchTaskFailed(mActivity);
                                         nextTask.notifyTaskLaunchFailed(TAG);
                                     } else {
-                                        mActivityControlHelper.onLaunchTaskSuccess(mActivity);
+                                        mActivityInterface.onLaunchTaskSuccess(mActivity);
                                     }
                                 }, mMainThreadHandler);
                     }
@@ -256,15 +262,37 @@
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
     }
 
+    /**
+     * Runs the given {@param action} if the recents animation has already started, or queues it to
+     * be run when it is next started.
+     */
+    protected void runOnRecentsAnimationStart(Runnable action) {
+        if (mRecentsAnimationTargets == null) {
+            mRecentsAnimationStartCallbacks.add(action);
+        } else {
+            action.run();
+        }
+    }
+
+    /**
+     * @return whether the recents animation has started and there are valid app targets.
+     */
+    protected boolean hasTargets() {
+        return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
+    }
+
     @Override
-    public void onRecentsAnimationStart(RecentsAnimationTargets targetSet) {
+    public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
+            RecentsAnimationTargets targets) {
+        mRecentsAnimationController = recentsAnimationController;
+        mRecentsAnimationTargets = targets;
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext);
         final Rect overviewStackBounds;
-        RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mRunningTaskId);
 
-        if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) {
-            overviewStackBounds = mActivityControlHelper
-                    .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
+        if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+            overviewStackBounds = mActivityInterface
+                    .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
             dp = dp.getMultiWindowProfile(mContext, new Point(
                     overviewStackBounds.width(), overviewStackBounds.height()));
         } else {
@@ -272,7 +300,7 @@
             dp = dp.copy(mContext);
             overviewStackBounds = getStackBounds(dp);
         }
-        dp.updateInsets(targetSet.homeContentInsets);
+        dp.updateInsets(targets.homeContentInsets);
         dp.updateIsSeascape(mContext);
         if (runningTaskTarget != null) {
             mAppWindowAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
@@ -281,7 +309,25 @@
         mAppWindowAnimationHelper.prepareAnimation(dp, false /* isOpening */);
         initTransitionEndpoints(dp);
 
-        mRecentsAnimationWrapper.setController(targetSet);
+        // Notify when the animation starts
+        if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+            for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+                action.run();
+            }
+            mRecentsAnimationStartCallbacks.clear();
+        }
+    }
+
+    @Override
+    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
+    }
+
+    @Override
+    public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
     }
 
     private Rect getStackBounds(DeviceProfile dp) {
@@ -299,7 +345,7 @@
     protected void initTransitionEndpoints(DeviceProfile dp) {
         mDp = dp;
 
-        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
+        mTransitionDragLength = mActivityInterface.getSwipeUpDestinationAndLength(
                 dp, mContext, TEMP_RECT);
         if (!dp.isMultiWindowMode) {
             // When updating the target rect, also update the home bounds since the location on
@@ -370,7 +416,7 @@
         mTransformParams.setProgress(shift)
                 .setOffsetX(offsetX)
                 .setOffsetScale(offsetScale)
-                .setTargetSet(mRecentsAnimationWrapper.targetSet)
+                .setTargetSet(mRecentsAnimationTargets)
                 .setLauncherOnTop(true);
         mAppWindowAnimationHelper.applyTransform(mTransformParams);
     }
@@ -388,11 +434,10 @@
      */
     protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
             HomeAnimationFactory homeAnimationFactory) {
-        final RemoteAnimationTargets targetSet = mRecentsAnimationWrapper.targetSet;
         final RectF startRect = new RectF(
                 mAppWindowAnimationHelper.applyTransform(
                         mTransformParams.setProgress(startProgress)
-                                .setTargetSet(targetSet)
+                                .setTargetSet(mRecentsAnimationTargets)
                                 .setLauncherOnTop(false)));
         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
 
@@ -472,7 +517,7 @@
 
     public interface Factory {
 
-        BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
+        BaseSwipeUpHandler newHandler(GestureState gestureState, RunningTaskInfo runningTask,
                 long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
similarity index 94%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 04ece7c..8deb835 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -37,7 +37,6 @@
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.RemoteAnimationTargets;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -45,14 +44,14 @@
 import java.util.function.Consumer;
 
 /**
- * {@link ActivityControlHelper} for recents when the default launcher is different than the
+ * {@link BaseActivityInterface} for recents when the default launcher is different than the
  * currently running one and apps should interact with the {@link RecentsActivity} as opposed
  * to the in-launcher one.
  */
-public final class FallbackActivityControllerHelper implements
-        ActivityControlHelper<RecentsActivity> {
+public final class FallbackActivityInterface implements
+        BaseActivityInterface<RecentsActivity> {
 
-    public FallbackActivityControllerHelper() { }
+    public FallbackActivityInterface() { }
 
     @Override
     public void onTransitionCancelled(RecentsActivity activity, boolean activityVisible) {
@@ -143,12 +142,12 @@
                 if (!isAnimatingToRecents) {
                     rv.setContentAlpha(1);
                 }
-                createActivityController(getSwipeUpDestinationAndLength(
+                createActivityInterface(getSwipeUpDestinationAndLength(
                         activity.getDeviceProfile(), activity, new Rect()));
             }
 
             @Override
-            public void createActivityController(long transitionLength) {
+            public void createActivityInterface(long transitionLength) {
                 AnimatorSet animatorSet = new AnimatorSet();
                 if (isAnimatingToRecents) {
                     ObjectAnimator anim = ObjectAnimator.ofFloat(rv, CONTENT_ALPHA, 0, 1);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index c749761..f6b3654 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -73,9 +73,9 @@
 import java.util.function.Consumer;
 
 /**
- * {@link ActivityControlHelper} for the in-launcher recents.
+ * {@link BaseActivityInterface} for the in-launcher recents.
  */
-public final class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
+public final class LauncherActivityInterface implements BaseActivityInterface<Launcher> {
 
     private Runnable mAdjustInterpolatorsRunnable;
 
@@ -214,8 +214,8 @@
             private boolean mIsAttachedToWindow;
 
             @Override
-            public void createActivityController(long transitionLength) {
-                createActivityControllerInternal(activity, fromState, transitionLength, callback);
+            public void createActivityInterface(long transitionLength) {
+                createActivityInterfaceInternal(activity, 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.
@@ -312,7 +312,7 @@
         };
     }
 
-    private void createActivityControllerInternal(Launcher activity, LauncherState fromState,
+    private void createActivityInterfaceInternal(Launcher activity, LauncherState fromState,
             long transitionLength, Consumer<AnimatorPlaybackController> callback) {
         LauncherState endState = OVERVIEW;
         if (fromState == endState) {
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 6df9ce5..150c44d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -27,6 +27,7 @@
 import android.os.SystemClock;
 import android.view.ViewConfiguration;
 
+import androidx.annotation.BinderThread;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -44,37 +45,43 @@
 public class OverviewCommandHelper {
 
     private final Context mContext;
-    private final ActivityManagerWrapper mAM;
+    private final RecentsAnimationDeviceState mDeviceState;
     private final RecentsModel mRecentsModel;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
 
-    public OverviewCommandHelper(Context context, OverviewComponentObserver observer) {
+    public OverviewCommandHelper(Context context, RecentsAnimationDeviceState deviceState,
+            OverviewComponentObserver observer) {
         mContext = context;
-        mAM = ActivityManagerWrapper.getInstance();
+        mDeviceState = deviceState;
         mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
 
+    @BinderThread
     public void onOverviewToggle() {
         // If currently screen pinning, do not enter overview
-        if (mAM.isScreenPinningActive()) {
+        if (mDeviceState.isScreenPinningActive()) {
             return;
         }
 
-        mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        ActivityManagerWrapper.getInstance()
+                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
         MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
+    @BinderThread
     public void onOverviewShown(boolean triggeredFromAltTab) {
         MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
     }
 
+    @BinderThread
     public void onOverviewHidden() {
         MAIN_EXECUTOR.execute(new HideRecentsCommand());
     }
 
+    @BinderThread
     public void onTip(int actionType, int viewType) {
         MAIN_EXECUTOR.execute(() ->
                 UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
@@ -91,14 +98,14 @@
         @Override
         protected boolean handleCommand(long elapsedTime) {
             // TODO: Go to the next page if started from alt-tab.
-            return mHelper.getVisibleRecentsView() != null;
+            return mActivityInterface.getVisibleRecentsView() != null;
         }
 
         @Override
         protected void onTransitionComplete() {
             // TODO(b/138729100) This doesn't execute first time launcher is run
             if (mTriggeredFromAltTab) {
-                RecentsView rv = (RecentsView) mHelper.getVisibleRecentsView();
+                RecentsView rv = (RecentsView) mActivityInterface.getVisibleRecentsView();
                 if (rv == null) {
                     return;
                 }
@@ -123,7 +130,7 @@
 
         @Override
         protected boolean handleCommand(long elapsedTime) {
-            RecentsView recents = (RecentsView) mHelper.getVisibleRecentsView();
+            RecentsView recents = (RecentsView) mActivityInterface.getVisibleRecentsView();
             if (recents == null) {
                 return false;
             }
@@ -139,7 +146,7 @@
 
     private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
 
-        protected final ActivityControlHelper<T> mHelper;
+        protected final BaseActivityInterface<T> mActivityInterface;
         private final long mCreateTime;
         private final AppToOverviewAnimationProvider<T> mAnimationProvider;
 
@@ -148,10 +155,10 @@
         private ActivityInitListener mListener;
 
         public RecentsActivityCommand() {
-            mHelper = mOverviewComponentObserver.getActivityControlHelper();
+            mActivityInterface = mOverviewComponentObserver.getActivityInterface();
             mCreateTime = SystemClock.elapsedRealtime();
-            mAnimationProvider =
-                    new AppToOverviewAnimationProvider<>(mHelper, RecentsModel.getRunningTaskId());
+            mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
+                    RecentsModel.getRunningTaskId());
 
             // Preload the plan
             mRecentsModel.getTasks(null);
@@ -167,13 +174,13 @@
                 return;
             }
 
-            if (mHelper.switchToRecentsIfVisible(this::onTransitionComplete)) {
+            if (mActivityInterface.switchToRecentsIfVisible(this::onTransitionComplete)) {
                 // If successfully switched, then return
                 return;
             }
 
             // Otherwise, start overview.
-            mListener = mHelper.createActivityInitListener(this::onActivityReady);
+            mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
             mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
                     this::createWindowAnimation, mContext, MAIN_EXECUTOR.getHandler(),
                     mAnimationProvider.getRecentsLaunchDuration());
@@ -183,7 +190,7 @@
             // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
             //       the menu activity which takes window focus, preventing the right condition from
             //       being run below
-            RecentsView recents = mHelper.getVisibleRecentsView();
+            RecentsView recents = mActivityInterface.getVisibleRecentsView();
             if (recents != null) {
                 // Launch the next task
                 recents.showNextTask();
@@ -200,7 +207,7 @@
             if (!mUserEventLogged) {
                 activity.getUserEventDispatcher().logActionCommand(
                         LauncherLogProto.Action.Command.RECENTS_BUTTON,
-                        mHelper.getContainerType(),
+                        mActivityInterface.getContainerType(),
                         LauncherLogProto.ContainerType.TASKSWITCHER);
                 mUserEventLogged = true;
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 6ac7546..1bbeca4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -83,7 +83,7 @@
         OverviewComponentObserver observer = new OverviewComponentObserver(mContext,
                 new RecentsAnimationDeviceState(mContext));
         try {
-            return observer.getActivityControlHelper().getCreatedActivity().getOverviewPanel();
+            return observer.getActivityInterface().getCreatedActivity().getOverviewPanel();
         } finally {
             observer.onDestroy();
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
deleted file mode 100644
index 43c4a0b..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.inputconsumers.InputConsumer;
-import com.android.quickstep.util.RecentsAnimationTargets;
-import com.android.systemui.shared.system.InputConsumerController;
-
-import java.util.ArrayList;
-import java.util.function.Supplier;
-
-/**
- * Wrapper around RecentsAnimationController to help with some synchronization
- */
-public class RecentsAnimationWrapper {
-
-    private static final String TAG = "RecentsAnimationWrapper";
-
-    // A list of callbacks to run when we receive the recents animation target. There are different
-    // than the state callbacks as these run on the current worker thread.
-    private final ArrayList<Runnable> mCallbacks = new ArrayList<>();
-
-    public RecentsAnimationTargets targetSet;
-
-    private boolean mWindowThresholdCrossed = false;
-
-    private final InputConsumerController mInputConsumerController;
-    private final Supplier<InputConsumer> mInputProxySupplier;
-
-    private InputConsumer mInputConsumer;
-    private boolean mTouchInProgress;
-
-    private boolean mFinishPending;
-
-    public RecentsAnimationWrapper(InputConsumerController inputConsumerController,
-            Supplier<InputConsumer> inputProxySupplier) {
-        mInputConsumerController = inputConsumerController;
-        mInputProxySupplier = inputProxySupplier;
-    }
-
-    public boolean hasTargets() {
-        return targetSet != null && targetSet.hasTargets();
-    }
-
-    @UiThread
-    public synchronized void setController(RecentsAnimationTargets targetSet) {
-        Preconditions.assertUIThread();
-        this.targetSet = targetSet;
-
-        if (targetSet == null) {
-            return;
-        }
-        targetSet.setWindowThresholdCrossed(mWindowThresholdCrossed);
-
-        if (!mCallbacks.isEmpty()) {
-            for (Runnable action : new ArrayList<>(mCallbacks)) {
-                action.run();
-            }
-            mCallbacks.clear();
-        }
-    }
-
-    public synchronized void runOnInit(Runnable action) {
-        if (targetSet == null) {
-            mCallbacks.add(action);
-        } else {
-            action.run();
-        }
-    }
-
-    /** See {@link #finish(boolean, Runnable, boolean)} */
-    @UiThread
-    public void finish(boolean toRecents, Runnable onFinishComplete) {
-        finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
-    }
-
-    /**
-     * @param onFinishComplete A callback that runs on the main thread after the animation
-     *                         controller has finished on the background thread.
-     * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
-     *                          activity. If userLeaveHint is true, the activity will enter into
-     *                          picture-in-picture mode upon being paused.
-     */
-    @UiThread
-    public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
-        Preconditions.assertUIThread();
-        if (!toRecents) {
-            finishAndClear(false, onFinishComplete, sendUserLeaveHint);
-        } else {
-            if (mTouchInProgress) {
-                mFinishPending = true;
-                // Execute the callback
-                if (onFinishComplete != null) {
-                    onFinishComplete.run();
-                }
-            } else {
-                finishAndClear(true, onFinishComplete, sendUserLeaveHint);
-            }
-        }
-    }
-
-    private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
-            boolean sendUserLeaveHint) {
-        RecentsAnimationTargets controller = targetSet;
-        targetSet = null;
-        disableInputProxy();
-        if (controller != null) {
-            controller.finishController(toRecents, onFinishComplete, sendUserLeaveHint);
-        }
-    }
-
-    public void enableInputConsumer() {
-        if (targetSet != null) {
-            targetSet.enableInputConsumer();
-        }
-    }
-
-    /**
-     * Indicates that the gesture has crossed the window boundary threshold and system UI can be
-     * update the represent the window behind
-     */
-    public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
-        if (mWindowThresholdCrossed != windowThresholdCrossed) {
-            mWindowThresholdCrossed = windowThresholdCrossed;
-            if (targetSet != null) {
-                targetSet.setWindowThresholdCrossed(windowThresholdCrossed);
-            }
-        }
-    }
-
-    public void enableInputProxy() {
-        mInputConsumerController.setInputListener(this::onInputConsumerEvent);
-    }
-
-    private void disableInputProxy() {
-        if (mInputConsumer != null && mTouchInProgress) {
-            long now = SystemClock.uptimeMillis();
-            MotionEvent dummyCancel = MotionEvent.obtain(now,  now, ACTION_CANCEL, 0, 0, 0);
-            mInputConsumer.onMotionEvent(dummyCancel);
-            dummyCancel.recycle();
-        }
-        mInputConsumerController.setInputListener(null);
-    }
-
-    private boolean onInputConsumerEvent(InputEvent ev) {
-        if (ev instanceof MotionEvent) {
-            onInputConsumerMotionEvent((MotionEvent) ev);
-        } else if (ev instanceof KeyEvent) {
-            if (mInputConsumer == null) {
-                mInputConsumer = mInputProxySupplier.get();
-            }
-            mInputConsumer.onKeyEvent((KeyEvent) ev);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean onInputConsumerMotionEvent(MotionEvent ev) {
-        int action = ev.getAction();
-
-        // Just to be safe, verify that ACTION_DOWN comes before any other action,
-        // and ignore any ACTION_DOWN after the first one (though that should not happen).
-        if (!mTouchInProgress && action != ACTION_DOWN) {
-            Log.w(TAG, "Received non-down motion before down motion: " + action);
-            return false;
-        }
-        if (mTouchInProgress && action == ACTION_DOWN) {
-            Log.w(TAG, "Received down motion while touch was already in progress");
-            return false;
-        }
-
-        if (action == ACTION_DOWN) {
-            mTouchInProgress = true;
-            if (mInputConsumer == null) {
-                mInputConsumer = mInputProxySupplier.get();
-            }
-        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
-            // Finish any pending actions
-            mTouchInProgress = false;
-            if (mFinishPending) {
-                mFinishPending = false;
-                finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
-            }
-        }
-        if (mInputConsumer != null) {
-            mInputConsumer.onMotionEvent(ev);
-        }
-
-        return true;
-    }
-
-    public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
-        if (targetSet != null) {
-            targetSet.controller.setDeferCancelUntilNextTransition(defer, screenshot);
-        }
-    }
-
-    public RecentsAnimationTargets getController() {
-        return targetSet;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index 6b98a89..cd8e1a4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -22,9 +22,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.RecentsAnimationCallbacks;
-import com.android.quickstep.util.RecentsAnimationTargets;
-import com.android.quickstep.util.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
 
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -38,11 +36,9 @@
     private OverviewComponentObserver mOverviewComponentObserver;
 
     private RecentsAnimationCallbacks mRecentsAnimationListener;
+    private RecentsAnimationController mLastRecentsAnimationController;
     private RecentsAnimationTargets mLastAnimationTarget;
 
-    // TODO: Remove
-    private Runnable mRecentsAnimationCanceledCallback;
-
     private boolean mLastAnimationCancelled = false;
     private boolean mLastAnimationRunning = false;
 
@@ -57,13 +53,35 @@
     }
 
     @Override
-    public final void onRecentsAnimationStart(RecentsAnimationTargets targetSet) {
-        mLastAnimationTarget = targetSet;
+    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();
@@ -71,42 +89,23 @@
         }
     }
 
-    @Override
-    public final void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        if (thumbnailData != null) {
-            mOverviewComponentObserver.getActivityControlHelper().switchToScreenshot(thumbnailData,
-                    () -> {
-                        if (mRecentsAnimationCanceledCallback != null) {
-                            mRecentsAnimationCanceledCallback.run();
-                        }
-                        clearAnimationState();
-                    });
-        } else {
-            clearAnimationState();
-        }
-    }
-
-    public void setRecentsAnimationCanceledCallback(Runnable callback) {
-        mRecentsAnimationCanceledCallback = callback;
-    }
-
     private void clearAnimationState() {
         clearAnimationTarget();
 
         mLastAnimationCancelled = true;
         mLastAnimationRunning = false;
-        mRecentsAnimationCanceledCallback = null;
     }
 
     private void clearListenerState(boolean finishAnimation) {
         if (mRecentsAnimationListener != null) {
             mRecentsAnimationListener.removeListener(this);
-            mRecentsAnimationListener.cancelListener();
-            if (mLastAnimationRunning && mLastAnimationTarget != null) {
+            mRecentsAnimationListener.notifyAnimationCanceled();
+            if (mLastAnimationRunning && mLastRecentsAnimationController != null) {
                 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
                         finishAnimation
-                                ? mLastAnimationTarget::finishAnimation
-                                : mLastAnimationTarget::cancelAnimation);
+                                ? mLastRecentsAnimationController::finishAnimationToHome
+                                : mLastRecentsAnimationController::finishAnimationToApp);
+                mLastRecentsAnimationController = null;
                 mLastAnimationTarget = null;
             }
         }
@@ -116,13 +115,7 @@
         mLastAnimationRunning = false;
     }
 
-    private void onSwipeAnimationFinished(RecentsAnimationTargets targetSet) {
-        if (mLastAnimationTarget == targetSet) {
-            mLastAnimationRunning = false;
-        }
-    }
-
-    public RecentsAnimationCallbacks newRecentsAnimationListenerSet() {
+    public RecentsAnimationCallbacks newRecentsAnimationCallbacks() {
         Preconditions.assertUIThread();
 
         if (mLastAnimationRunning) {
@@ -136,9 +129,8 @@
 
         clearListenerState(false /* finishAnimation */);
         boolean shouldMinimiseSplitScreen = mOverviewComponentObserver == null ? false
-                : mOverviewComponentObserver.getActivityControlHelper().shouldMinimizeSplitScreen();
-        mRecentsAnimationListener = new RecentsAnimationCallbacks(
-                shouldMinimiseSplitScreen, this::onSwipeAnimationFinished);
+                : mOverviewComponentObserver.getActivityInterface().shouldMinimizeSplitScreen();
+        mRecentsAnimationListener = new RecentsAnimationCallbacks(shouldMinimiseSplitScreen);
         mRecentsAnimationListener.addListener(this);
         return mRecentsAnimationListener;
     }
@@ -148,8 +140,9 @@
     }
 
     public void applyActiveRecentsAnimationState(RecentsAnimationListener listener) {
-        if (mLastAnimationTarget != null) {
-            listener.onRecentsAnimationStart(mLastAnimationTarget);
+        if (mLastRecentsAnimationController != null) {
+            listener.onRecentsAnimationStart(mLastRecentsAnimationController,
+                    mLastAnimationTarget);
         } else if (mLastAnimationCancelled) {
             listener.onRecentsAnimationCanceled(null);
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
index 1af0db0..5a2e3ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
@@ -28,9 +28,7 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.Log;
 import android.view.View;
 
 import com.android.launcher3.BaseDraggingActivity;
@@ -44,7 +42,6 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
@@ -240,13 +237,7 @@
 
         @Override
         protected boolean onActivityStarted(BaseDraggingActivity activity) {
-            ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
-            try {
-                sysUiProxy.onSplitScreenInvoked();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to notify SysUI of split screen: ", e);
-                return false;
-            }
+            SystemUiProxy.INSTANCE.get(activity).onSplitScreenInvoked();
             activity.getUserEventDispatcher().logActionOnControl(TAP,
                     LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
             return true;
@@ -293,8 +284,7 @@
         @Override
         public View.OnClickListener getOnClickListener(
                 BaseDraggingActivity activity, TaskView taskView) {
-            ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
-            if (sysUiProxy == null) {
+            if (!SystemUiProxy.INSTANCE.get(activity).isActive()) {
                 return null;
             }
             if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
@@ -307,11 +297,8 @@
             return view -> {
                 Consumer<Boolean> resultCallback = success -> {
                     if (success) {
-                        try {
-                            sysUiProxy.startScreenPinning(taskView.getTask().key.id);
-                        } catch (RemoteException e) {
-                            Log.w(TAG, "Failed to start screen pinning: ", e);
-                        }
+                        SystemUiProxy.INSTANCE.get(activity).startScreenPinning(
+                                taskView.getTask().key.id);
                     } else {
                         taskView.notifyTaskLaunchFailed(TAG);
                     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index b55fd8b..2522c0f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -33,7 +33,6 @@
 import com.android.launcher3.Utilities;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
-import com.android.quickstep.util.RemoteAnimationTargets;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
@@ -121,13 +120,13 @@
             RemoteAnimationTargetCompat[] wallpaperTargets, final AppWindowAnimationHelper inOutHelper) {
         SyncRtSurfaceTransactionApplierCompat applier =
                 new SyncRtSurfaceTransactionApplierCompat(v);
-        final RemoteAnimationTargets targetSet =
+        final RemoteAnimationTargets targets =
                 new RemoteAnimationTargets(appTargets, wallpaperTargets, MODE_OPENING);
-        targetSet.addDependentTransactionApplier(applier);
+        targets.addDependentTransactionApplier(applier);
         AppWindowAnimationHelper.TransformParams params =
                 new AppWindowAnimationHelper.TransformParams()
                     .setSyncTransactionApplier(applier)
-                    .setTargetSet(targetSet)
+                    .setTargetSet(targets)
                     .setLauncherOnTop(true);
 
         final RecentsView recentsView = v.getRecentsView();
@@ -149,7 +148,7 @@
                         BaseActivity.fromContext(v.getContext()).getDeviceProfile(),
                         true /* isOpening */);
                 inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
-                        targetSet.apps.length == 0 ? null : targetSet.apps[0]);
+                        targets.apps.length == 0 ? null : targets.apps[0]);
 
                 mThumbnailRect = new RectF(inOutHelper.getTargetRect());
                 mThumbnailRect.offset(-v.getTranslationX(), -v.getTranslationY());
@@ -203,7 +202,7 @@
         appAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                targetSet.release();
+                targets.release();
             }
         });
         return appAnimator;
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 708408a..4b7ae6f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -36,23 +36,25 @@
 import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.RemoteException;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputEvent;
 import android.view.MotionEvent;
 
+import androidx.annotation.BinderThread;
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.AppLaunchTracker;
@@ -65,11 +67,10 @@
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
 import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer;
-import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewWithoutFocusInputConsumer;
-import com.android.quickstep.inputconsumers.QuickCaptureTouchConsumer;
+import com.android.quickstep.inputconsumers.QuickCaptureInputConsumer;
 import com.android.quickstep.inputconsumers.ResetGestureInputConsumer;
 import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -116,30 +117,36 @@
 
     private static final String KEY_BACK_NOTIFICATION_COUNT = "backNotificationCount";
     private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
+    private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
     private static final int MAX_BACK_NOTIFICATION_COUNT = 3;
     private int mBackGestureNotificationCounter = -1;
 
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
+        @BinderThread
         public void onInitialize(Bundle bundle) {
-            mISystemUiProxy = ISystemUiProxy.Stub
-                    .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
+            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(TouchInteractionService.this::onSystemUiProxySet);
             MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */));
             sIsInitialized = true;
         }
 
+        @BinderThread
         @Override
         public void onOverviewToggle() {
             mOverviewCommandHelper.onOverviewToggle();
         }
 
+        @BinderThread
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
             mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
         }
 
+        @BinderThread
         @Override
         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
             if (triggeredFromAltTab && !triggeredFromHomeKey) {
@@ -148,44 +155,50 @@
             }
         }
 
+        @BinderThread
         @Override
         public void onTip(int actionType, int viewType) {
             mOverviewCommandHelper.onTip(actionType, viewType);
         }
 
+        @BinderThread
         @Override
         public void onAssistantAvailable(boolean available) {
             MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantAvailable(available));
             MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged);
         }
 
+        @BinderThread
         @Override
         public void onAssistantVisibilityChanged(float visibility) {
             MAIN_EXECUTOR.execute(() -> mDeviceState.setAssistantVisibility(visibility));
             MAIN_EXECUTOR.execute(TouchInteractionService.this::onAssistantVisibilityChanged);
         }
 
+        @BinderThread
         public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
                 boolean gestureSwipeLeft) {
             if (mOverviewComponentObserver == null) {
                 return;
             }
 
-            final ActivityControlHelper activityControl =
-                    mOverviewComponentObserver.getActivityControlHelper();
+            final BaseActivityInterface activityInterface =
+                    mOverviewComponentObserver.getActivityInterface();
             UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
-                    isButton, gestureSwipeLeft, activityControl.getContainerType());
+                    isButton, gestureSwipeLeft, activityInterface.getContainerType());
 
             if (completed && !isButton && shouldNotifyBackGesture()) {
                 UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
             }
         }
 
+        @BinderThread
         public void onSystemUiStateChanged(int stateFlags) {
             MAIN_EXECUTOR.execute(() -> mDeviceState.setSystemUiFlags(stateFlags));
             MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged);
         }
 
+        @BinderThread
         public void onActiveNavBarRegionChanges(Region region) {
             MAIN_EXECUTOR.execute(() -> mDeviceState.setDeferredGestureRegion(region));
         }
@@ -235,10 +248,8 @@
 
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
-    private ISystemUiProxy mISystemUiProxy;
     private OverviewCommandHelper mOverviewCommandHelper;
     private OverviewComponentObserver mOverviewComponentObserver;
-    private OverviewInteractionState mOverviewInteractionState;
     private InputConsumerController mInputConsumer;
     private RecentsAnimationDeviceState mDeviceState;
 
@@ -284,24 +295,20 @@
             Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 1");
         }
         disposeEventHandlers();
-        if (!mMode.hasGestures || mISystemUiProxy == null) {
+        if (!mMode.hasGestures || !SystemUiProxy.INSTANCE.get(this).isActive()) {
             return;
         }
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 2");
         }
 
-        try {
-            mInputMonitorCompat = InputMonitorCompat.fromBundle(mISystemUiProxy
-                    .monitorGestureInput("swipe-up", mDeviceState.getDisplayId()),
-                    KEY_EXTRA_INPUT_MONITOR);
-            mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
-                    mMainChoreographer, this::onInputEvent);
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 3");
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to create input monitor", e);
+        Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
+                mDeviceState.getDisplayId());
+        mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
+        mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
+                mMainChoreographer, this::onInputEvent);
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_BACKGROUND_TO_OVERVIEW_TAG, "initInputMonitor 3");
         }
 
         mDeviceState.updateGestureTouchRegions();
@@ -314,19 +321,19 @@
         }
         mMode = newMode;
         initInputMonitor();
+        resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
+    @UiThread
     public void onUserUnlocked() {
         mRecentsModel = RecentsModel.INSTANCE.get(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this, mDeviceState);
-
-        mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
-        mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this);
+        mOverviewCommandHelper = new OverviewCommandHelper(this, mDeviceState,
+                mOverviewComponentObserver);
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
 
         sSwipeSharedState.setOverviewComponentObserver(mOverviewComponentObserver);
         mInputConsumer.registerInputConsumer();
-        onSystemUiProxySet();
         onSystemUiFlagsChanged();
         onAssistantVisibilityChanged();
 
@@ -334,20 +341,31 @@
         // new ModelPreload().start(this);
         mBackGestureNotificationCounter = Math.max(0, Utilities.getDevicePrefs(this)
                 .getInt(KEY_BACK_NOTIFICATION_COUNT, MAX_BACK_NOTIFICATION_COUNT));
+        resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
-    @UiThread
-    private void onSystemUiProxySet() {
-        if (mDeviceState.isUserUnlocked()) {
-            mRecentsModel.setSystemUiProxy(mISystemUiProxy);
-            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+    private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
+        if (!mDeviceState.isUserUnlocked() || !mMode.hasGestures) {
+            // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
+            // mode doesn't have gestures
+            return;
+        }
+
+        // Reset home bounce seen on quick step enabled for first time
+        SharedPreferences sharedPrefs = Utilities.getPrefs(this);
+        if (!sharedPrefs.getBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)) {
+            sharedPrefs.edit()
+                    .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
+                    .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
+                    .apply();
         }
     }
 
     @UiThread
     private void onSystemUiFlagsChanged() {
         if (mDeviceState.isUserUnlocked()) {
-            mOverviewInteractionState.setSystemUiStateFlags(mDeviceState.getSystemUiStateFlags());
+            SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(
+                    mDeviceState.getSystemUiStateFlags());
             mOverviewComponentObserver.onSystemUiStateChanged();
         }
     }
@@ -355,7 +373,7 @@
     @UiThread
     private void onAssistantVisibilityChanged() {
         if (mDeviceState.isUserUnlocked()) {
-            mOverviewComponentObserver.getActivityControlHelper().onAssistantVisibilityChanged(
+            mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
                     mDeviceState.getAssistantVisibility());
         }
     }
@@ -368,8 +386,9 @@
             mOverviewComponentObserver.onDestroy();
         }
         disposeEventHandlers();
-        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
         mDeviceState.destroy();
+        SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this);
+        SystemUiProxy.INSTANCE.get(this).setProxy(null);
 
         sConnected = false;
         super.onDestroy();
@@ -393,13 +412,16 @@
 
         MotionEvent event = (MotionEvent) ev;
         if (event.getAction() == ACTION_DOWN) {
+            GestureState newGestureState = new GestureState(
+                    mOverviewComponentObserver.getActivityInterface());
+
             mLogId = ActiveGestureLog.INSTANCE.generateAndSetLogId();
             sSwipeSharedState.setLogTraceId(mLogId);
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
                 boolean useSharedState = mConsumer.useSharedSwipeState();
                 mConsumer.onConsumerAboutToBeSwitched();
-                mConsumer = newConsumer(useSharedState, event);
+                mConsumer = newConsumer(newGestureState, useSharedState, event);
                 ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
                 mUncheckedConsumer = mConsumer;
             } else if (mDeviceState.isUserUnlocked() && mMode == Mode.NO_BUTTON
@@ -407,10 +429,8 @@
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
                 // not interrupt it. QuickSwitch assumes that interruption can only happen if the
                 // next gesture is also quick switch.
-                mUncheckedConsumer =
-                        new AssistantInputConsumer(this, mISystemUiProxy,
-                                mOverviewComponentObserver.getActivityControlHelper(),
-                                InputConsumer.NO_OP, mInputMonitorCompat);
+                mUncheckedConsumer = new AssistantInputConsumer(this, newGestureState,
+                        InputConsumer.NO_OP, mInputMonitorCompat);
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
@@ -421,14 +441,16 @@
         DejankBinderTracker.disallowBinderTrackingInTests();
     }
 
-    private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
+    private InputConsumer newConsumer(GestureState gestureState, boolean useSharedState,
+            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(mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
+                return createDeviceLockedInputConsumer(gestureState,
+                        mAM.getRunningTask(ACTIVITY_TYPE_ASSISTANT));
             } else {
                 return mResetGestureInputConsumer;
             }
@@ -437,30 +459,25 @@
         // 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(useSharedState, event) : mResetGestureInputConsumer;
+                ? newBaseConsumer(gestureState, useSharedState, event) : mResetGestureInputConsumer;
         if (mMode == Mode.NO_BUTTON) {
-            final ActivityControlHelper activityControl =
-                    mOverviewComponentObserver.getActivityControlHelper();
             if (mDeviceState.canTriggerAssistantAction(event)) {
-                base = new AssistantInputConsumer(this, mISystemUiProxy, activityControl, base,
-                        mInputMonitorCompat);
+                base = new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat);
             }
 
             if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
                 // Put the Compose gesture as higher priority than the Assistant or base gestures
-                base = new QuickCaptureTouchConsumer(this, base,
-                    mInputMonitorCompat, mOverviewComponentObserver.getActivityControlHelper());
+                base = new QuickCaptureInputConsumer(this, gestureState, 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, mISystemUiProxy, activityControl);
+                base = new ScreenPinnedInputConsumer(this, gestureState);
             }
 
             if (mDeviceState.isAccessibilityMenuAvailable()) {
-                base = new AccessibilityInputConsumer(this, mDeviceState, mISystemUiProxy, base,
+                base = new AccessibilityInputConsumer(this, mDeviceState, base,
                         mInputMonitorCompat);
             }
         } else {
@@ -471,7 +488,8 @@
         return base;
     }
 
-    private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) {
+    private InputConsumer newBaseConsumer(GestureState gestureState, boolean useSharedState,
+            MotionEvent event) {
         RunningTaskInfo runningTaskInfo = DejankBinderTracker.whitelistIpcs(
                 () -> mAM.getRunningTask(0));
         if (!useSharedState) {
@@ -479,12 +497,9 @@
         }
         if (mDeviceState.isKeyguardShowingOccluded()) {
             // This handles apps showing over the lockscreen (e.g. camera)
-            return createDeviceLockedInputConsumer(runningTaskInfo);
+            return createDeviceLockedInputConsumer(gestureState, runningTaskInfo);
         }
 
-        final ActivityControlHelper activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-
         boolean forceOverviewInputConsumer = false;
         if (isExcludedAssistant(runningTaskInfo)) {
             // In the case where we are in the excluded assistant state, ignore it and treat the
@@ -507,16 +522,18 @@
             // consumer but with the next task as the running task
             RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
             info.id = sSwipeSharedState.nextRunningTaskId;
-            return createOtherActivityInputConsumer(event, info);
-        } else if (sSwipeSharedState.goingToLauncher || activityControl.isResumed()
+            return createOtherActivityInputConsumer(gestureState, event, info);
+        } else if (sSwipeSharedState.goingToLauncher
+                || gestureState.getActivityInterface().isResumed()
                 || forceOverviewInputConsumer) {
-            return createOverviewInputConsumer(event);
-        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityControl.isInLiveTileMode()) {
-            return createOverviewInputConsumer(event);
+            return createOverviewInputConsumer(gestureState, event);
+        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
+                && gestureState.getActivityInterface().isInLiveTileMode()) {
+            return createOverviewInputConsumer(gestureState, event);
         } else if (mDeviceState.isGestureBlockedActivity(runningTaskInfo)) {
             return mResetGestureInputConsumer;
         } else {
-            return createOtherActivityInputConsumer(event, runningTaskInfo);
+            return createOtherActivityInputConsumer(gestureState, event, runningTaskInfo);
         }
     }
 
@@ -526,52 +543,50 @@
                 && (info.baseIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
     }
 
-    private InputConsumer createOtherActivityInputConsumer(MotionEvent event,
-            RunningTaskInfo runningTaskInfo) {
+    private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
+            MotionEvent event, RunningTaskInfo runningTaskInfo) {
 
         final boolean shouldDefer;
         final BaseSwipeUpHandler.Factory factory;
-        ActivityControlHelper activityControlHelper =
-                mOverviewComponentObserver.getActivityControlHelper();
 
         if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
             shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
             factory = mFallbackNoButtonFactory;
         } else {
-            shouldDefer = activityControlHelper.deferStartingActivity(mDeviceState, event);
+            shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
+                    event);
             factory = mWindowTreansformFactory;
         }
 
         final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
-        return new OtherActivityInputConsumer(this, mDeviceState, runningTaskInfo, shouldDefer,
-                this::onConsumerInactive, sSwipeSharedState, mInputMonitorCompat,
-                disableHorizontalSwipe, activityControlHelper, factory, mLogId);
+        return new OtherActivityInputConsumer(this, mDeviceState, gestureState, runningTaskInfo,
+                shouldDefer, this::onConsumerInactive, sSwipeSharedState, mInputMonitorCompat,
+                disableHorizontalSwipe, factory, mLogId);
     }
 
-    private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
+    private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState,
+            RunningTaskInfo taskInfo) {
         if (mMode == Mode.NO_BUTTON && taskInfo != null) {
-            return new DeviceLockedInputConsumer(this, mDeviceState, sSwipeSharedState,
-                    mInputMonitorCompat, taskInfo.taskId, mLogId);
+            return new DeviceLockedInputConsumer(this, mDeviceState, gestureState,
+                    sSwipeSharedState, mInputMonitorCompat, taskInfo.taskId, mLogId);
         } else {
             return mResetGestureInputConsumer;
         }
     }
 
-    public InputConsumer createOverviewInputConsumer(MotionEvent event) {
-        final ActivityControlHelper activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-        BaseDraggingActivity activity = activityControl.getCreatedActivity();
+    public InputConsumer createOverviewInputConsumer(GestureState gestureState, MotionEvent event) {
+        BaseDraggingActivity activity = gestureState.getActivityInterface().getCreatedActivity();
         if (activity == null) {
             return mResetGestureInputConsumer;
         }
 
         if (activity.getRootView().hasWindowFocus() || sSwipeSharedState.goingToLauncher) {
-            return new OverviewInputConsumer(activity, mInputMonitorCompat,
-                    false /* startingInActivityBounds */, activityControl);
+            return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
+                    false /* startingInActivityBounds */);
         } else {
             final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
-            return new OverviewWithoutFocusInputConsumer(activity, mInputMonitorCompat,
-                    activityControl, disableHorizontalSwipe);
+            return new OverviewWithoutFocusInputConsumer(activity, gestureState,
+                    mInputMonitorCompat, disableHorizontalSwipe);
         }
     }
 
@@ -600,11 +615,11 @@
             return;
         }
 
-        final ActivityControlHelper<BaseDraggingActivity> activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-        if (activityControl.getCreatedActivity() == null) {
+        final BaseActivityInterface<BaseDraggingActivity> activityInterface =
+                mOverviewComponentObserver.getActivityInterface();
+        if (activityInterface.getCreatedActivity() == null) {
             // Make sure that UI states will be initialized.
-            activityControl.createActivityInitListener((activity, wasVisible) -> {
+            activityInterface.createActivityInitListener((activity, wasVisible) -> {
                 AppLaunchTracker.INSTANCE.get(activity);
                 return false;
             }).register();
@@ -625,9 +640,9 @@
         if (!mDeviceState.isUserUnlocked()) {
             return;
         }
-        final ActivityControlHelper activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-        final BaseDraggingActivity activity = activityControl.getCreatedActivity();
+        final BaseActivityInterface activityInterface =
+                mOverviewComponentObserver.getActivityInterface();
+        final BaseDraggingActivity activity = activityInterface.getCreatedActivity();
         if (activity == null || activity.isStarted()) {
             // We only care about the existing background activity.
             return;
@@ -659,7 +674,7 @@
             pw.println("TouchState:");
             pw.println("  navMode=" + mMode);
             boolean resumed = mOverviewComponentObserver != null
-                    && mOverviewComponentObserver.getActivityControlHelper().isResumed();
+                    && mOverviewComponentObserver.getActivityInterface().isResumed();
             pw.println("  resumed=" + resumed);
             pw.println("  useSharedState=" + mConsumer.useSharedSwipeState());
             if (mConsumer.useSharedSwipeState()) {
@@ -690,16 +705,20 @@
         }
     }
 
-    private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
-            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        return  new WindowTransformSwipeHandler(mDeviceState, runningTask, this, touchTimeMs,
-                mOverviewComponentObserver, continuingLastGesture, mInputConsumer, mRecentsModel);
+    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);
     }
 
-    private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(RunningTaskInfo runningTask,
-            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask,
-                mRecentsModel, mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
+    private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState,
+            RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture,
+            boolean isLikelyToStartNewTask) {
+        return new FallbackNoButtonInputConsumer(this, gestureState, mOverviewComponentObserver,
+                runningTask, mRecentsModel, mInputConsumer, isLikelyToStartNewTask,
+                continuingLastGesture);
     }
 
     protected boolean shouldNotifyBackGesture() {
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 b0628e5..e3b12cb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -26,8 +26,8 @@
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
-import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
-import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
+import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.HIDE;
+import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.PEEK;
 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;
@@ -71,16 +71,14 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.ActivityControlHelper.AnimationFactory;
-import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.quickstep.BaseActivityInterface.AnimationFactory;
+import com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState;
+import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
 import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.RecentsAnimationTargets;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -197,7 +195,8 @@
      */
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
-    private RecentsAnimationDeviceState mDeviceState;
+    private final RecentsAnimationDeviceState mDeviceState;
+    private final GestureState mGestureState;
 
     private GestureEndTarget mGestureEndTarget;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
@@ -230,12 +229,13 @@
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
-    public WindowTransformSwipeHandler(RecentsAnimationDeviceState deviceState,
-            RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
+    public WindowTransformSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+            GestureState gestureState, RunningTaskInfo runningTaskInfo, long touchTimeMs,
             OverviewComponentObserver overviewComponentObserver, boolean continuingLastGesture,
             InputConsumerController inputConsumer, RecentsModel recentsModel) {
-        super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+        super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
         mDeviceState = deviceState;
+        mGestureState = gestureState;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
         initStateCallbacks();
@@ -291,9 +291,6 @@
         mStateCallback.addCallback(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
                 this::notifyTransitionCancelled);
 
-        mStateCallback.addCallback(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
-                mRecentsAnimationWrapper::enableInputConsumer);
-
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mStateCallback.addChangeHandler(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
                             | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT,
@@ -354,7 +351,7 @@
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
         if (mGestureEndTarget != HOME) {
             Runnable initAnimFactory = () -> {
-                mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+                mAnimationFactory = mActivityInterface.prepareRecentsUI(mActivity,
                         mWasLauncherAlreadyVisible, true,
                         this::onAnimatorPlaybackControllerCreated);
                 maybeUpdateRecentsAttachedState(false /* animate */);
@@ -417,7 +414,7 @@
     }
 
     private void sendRemoteAnimationsToAnimationFactory() {
-        mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationWrapper.targetSet);
+        mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationTargets);
     }
 
     private void initializeLauncherAnimationController() {
@@ -464,9 +461,9 @@
         if (mMode != Mode.NO_BUTTON || mRecentsView == null) {
             return;
         }
-        RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationWrapper.targetSet == null
+        RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets == null
                 ? null
-                : mRecentsAnimationWrapper.targetSet.findTask(mRunningTaskId);
+                : mRecentsAnimationTargets.findTask(mRunningTaskId);
         final boolean recentsAttachedToAppWindow;
         int runningTaskIndex = mRecentsView.getRunningTaskIndex();
         if (mGestureEndTarget != null) {
@@ -523,7 +520,7 @@
             return;
         }
         initTransitionEndpoints(mActivity.getDeviceProfile());
-        mAnimationFactory.createActivityController(mTransitionDragLength);
+        mAnimationFactory.createActivityInterface(mTransitionDragLength);
     }
 
     @Override
@@ -548,15 +545,13 @@
 
     @Override
     public void updateFinalShift() {
-
-        RecentsAnimationTargets controller = mRecentsAnimationWrapper.getController();
-        if (controller != null) {
+        if (mRecentsAnimationTargets != null) {
             applyTransformUnchecked();
             updateSysUiFlags(mCurrentShift.value);
         }
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationWrapper.getController() != null) {
+            if (mRecentsAnimationTargets != null) {
                 mLiveTileOverlay.update(mAppWindowAnimationHelper.getCurrentRectWithInsets(),
                         mAppWindowAnimationHelper.getCurrentCornerRadius());
             }
@@ -599,17 +594,24 @@
                     : centermostTask.getThumbnail().getSysUiStatusNavFlags();
             boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
             // We will handle the sysui flags based on the centermost task view.
-            mRecentsAnimationWrapper.setWindowThresholdCrossed(centermostTaskFlags != 0
-                    || useHomeScreenFlags);
+            if (mRecentsAnimationController != null) {
+                mRecentsAnimationController.setWindowThresholdCrossed(centermostTaskFlags != 0
+                        || useHomeScreenFlags);
+            }
             int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags;
             mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
         }
     }
 
     @Override
-    public void onRecentsAnimationStart(RecentsAnimationTargets targetSet) {
-        super.onRecentsAnimationStart(targetSet);
-        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targetSet.apps.length);
+    public void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+        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,
+                mRecentsAnimationController::enableInputConsumer);
         setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
 
         mPassedOverviewThreshold = false;
@@ -617,7 +619,8 @@
 
     @Override
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        mRecentsAnimationWrapper.setController(null);
+        super.onRecentsAnimationCanceled(thumbnailData);
+        mRecentsView.setRecentsAnimationTargets(null, null);
         mActivityInitListener.unregister();
         setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
         ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
@@ -687,9 +690,9 @@
             setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
         }
 
-        BaseDraggingActivity activity = mActivityControlHelper.getCreatedActivity();
+        BaseDraggingActivity activity = mActivityInterface.getCreatedActivity();
         return activity == null ? InputConsumer.NO_OP
-                : new OverviewInputConsumer(activity, null, true, mActivityControlHelper);
+                : new OverviewInputConsumer(mGestureState, activity, null, true);
     }
 
     private void endRunningWindowAnim(boolean cancel) {
@@ -707,7 +710,7 @@
         final GestureEndTarget endTarget;
         final boolean goingToNewTask;
         if (mRecentsView != null) {
-            if (!mRecentsAnimationWrapper.hasTargets()) {
+            if (!hasTargets()) {
                 // If there are no running tasks, then we can assume that this is a continuation of
                 // the last gesture, but after the recents animation has finished
                 goingToNewTask = true;
@@ -810,8 +813,9 @@
             }
         }
 
-        if (endTarget.isLauncher) {
-            mRecentsAnimationWrapper.enableInputProxy();
+        if (endTarget.isLauncher && mRecentsAnimationController != null) {
+            mRecentsAnimationController.enableInputProxy(mInputConsumer,
+                    this::createNewInputProxyHandler);
         }
 
         if (endTarget == HOME) {
@@ -866,7 +870,7 @@
     @UiThread
     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
             GestureEndTarget target, PointF velocityPxPerMs) {
-        mRecentsAnimationWrapper.runOnInit(() -> animateToProgressInternal(start, end, duration,
+        runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
                 interpolator, target, velocityPxPerMs));
     }
 
@@ -880,7 +884,7 @@
         if (mGestureEndTarget == HOME) {
             HomeAnimationFactory homeAnimFactory;
             if (mActivity != null) {
-                homeAnimFactory = mActivityControlHelper.prepareHomeUI(mActivity);
+                homeAnimFactory = mActivityInterface.prepareHomeUI(mActivity);
             } else {
                 homeAnimFactory = new HomeAnimationFactory() {
                     @NonNull
@@ -992,7 +996,7 @@
                 }
                 // Make sure recents is in its final state
                 maybeUpdateRecentsAttachedState(false);
-                mActivityControlHelper.onSwipeUpToHomeComplete(mActivity);
+                mActivityInterface.onSwipeUpToHomeComplete(mActivity);
             }
         });
         return anim;
@@ -1018,7 +1022,7 @@
 
     @UiThread
     private void resumeLastTask() {
-        mRecentsAnimationWrapper.finish(false /* toRecents */, null);
+        mRecentsAnimationController.finish(false /* toRecents */, null);
         ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
         doLogGesture(LAST_TASK);
         reset();
@@ -1101,32 +1105,31 @@
 
     private void resetStateForAnimationCancel() {
         boolean wasVisible = mWasLauncherAlreadyVisible || mGestureStarted;
-        mActivityControlHelper.onTransitionCancelled(mActivity, wasVisible);
+        mActivityInterface.onTransitionCancelled(mActivity, wasVisible);
 
         // Leave the pending invisible flag, as it may be used by wallpaper open animation.
         mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
     }
 
     private void switchToScreenshot() {
-        RecentsAnimationTargets controller = mRecentsAnimationWrapper.getController();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (controller != null) {
+            if (mRecentsAnimationController != null) {
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
-                    mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
+                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
                 }
                 mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot, false /* refreshNow */);
             }
             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
-        } else if (!mRecentsAnimationWrapper.hasTargets()) {
+        } else if (!hasTargets()) {
             // If there are no targets, then we don't need to capture anything
             setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
         } else {
             boolean finishTransitionPosted = false;
-            if (controller != null) {
+            if (mRecentsAnimationController != null) {
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
-                    mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
+                    mTaskSnapshot = mRecentsAnimationController.screenshotTask(mRunningTaskId);
                 }
                 final TaskView taskView;
                 if (mGestureEndTarget == HOME) {
@@ -1155,12 +1158,12 @@
     private void finishCurrentTransitionToRecents() {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-        } else if (!mRecentsAnimationWrapper.hasTargets()) {
+        } else if (!hasTargets()) {
             // If there are no targets, then there is nothing to finish
             setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else {
-            synchronized (mRecentsAnimationWrapper) {
-                mRecentsAnimationWrapper.finish(true /* toRecents */,
+            synchronized (mRecentsAnimationController) {
+                mRecentsAnimationController.finish(true /* toRecents */,
                         () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
             }
         }
@@ -1168,8 +1171,8 @@
     }
 
     private void finishCurrentTransitionToHome() {
-        synchronized (mRecentsAnimationWrapper) {
-            mRecentsAnimationWrapper.finish(true /* toRecents */,
+        synchronized (mRecentsAnimationController) {
+            mRecentsAnimationController.finish(true /* toRecents */,
                     () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
                     true /* sendUserLeaveHint */);
         }
@@ -1179,9 +1182,11 @@
 
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
-        mActivityControlHelper.onSwipeUpToRecentsComplete(mActivity);
-        mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
-                true /* screenshot */);
+        mActivityInterface.onSwipeUpToRecentsComplete(mActivity);
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
+                    true /* screenshot */);
+        }
         mRecentsView.onSwipeUpAnimationSuccess();
 
         RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 92bcfb5..d3765c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -23,17 +23,16 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import android.content.Context;
-import android.os.RemoteException;
-import android.util.Log;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.R;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -43,7 +42,7 @@
 
     private static final String TAG = "A11yInputConsumer";
 
-    private final ISystemUiProxy mSystemUiProxy;
+    private final Context mContext;
     private final VelocityTracker mVelocityTracker;
     private final MotionPauseDetector mMotionPauseDetector;
     private final RecentsAnimationDeviceState mDeviceState;
@@ -56,9 +55,9 @@
     private float mTotalY;
 
     public AccessibilityInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
-            ISystemUiProxy systemUiProxy, InputConsumer delegate, InputMonitorCompat inputMonitor) {
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
-        mSystemUiProxy = systemUiProxy;
+        mContext = context;
         mVelocityTracker = VelocityTracker.obtain();
         mMinGestureDistance = context.getResources()
                 .getDimension(R.dimen.accessibility_gesture_min_swipe_distance);
@@ -126,22 +125,18 @@
             }
             case ACTION_UP:
                 if (mState == STATE_ACTIVE) {
-                    try {
-                        if (mDeviceState.isAccessibilityMenuShortcutAvailable()
-                                && mMotionPauseDetector.isPaused()) {
-                            mSystemUiProxy.notifyAccessibilityButtonLongClicked();
-                        } else {
-                            mTotalY += (ev.getY() - mDownY);
-                            mVelocityTracker.computeCurrentVelocity(1000);
+                    if (mDeviceState.isAccessibilityMenuShortcutAvailable()
+                            && mMotionPauseDetector.isPaused()) {
+                        SystemUiProxy.INSTANCE.get(mContext).notifyAccessibilityButtonLongClicked();
+                    } else {
+                        mTotalY += (ev.getY() - mDownY);
+                        mVelocityTracker.computeCurrentVelocity(1000);
 
-                            if ((-mTotalY) > mMinGestureDistance
-                                    || (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
-                                mSystemUiProxy.notifyAccessibilityButtonClicked(
-                                        Display.DEFAULT_DISPLAY);
-                            }
+                        if ((-mTotalY) > mMinGestureDistance
+                                || (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
+                            SystemUiProxy.INSTANCE.get(mContext).notifyAccessibilityButtonClicked(
+                                    Display.DEFAULT_DISPLAY);
                         }
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Unable to notify accessibility event", e);
                     }
                 }
                 // Follow through
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 7cec924..94126ff 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -37,9 +37,7 @@
 import android.content.res.Resources;
 import android.graphics.PointF;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.GestureDetector;
 import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.HapticFeedbackConstants;
@@ -50,8 +48,10 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.quickstep.ActivityControlHelper;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -81,24 +81,21 @@
     private long mDragTime;
     private float mLastProgress;
     private int mDirection;
-    private ActivityControlHelper mActivityControlHelper;
+    private BaseActivityInterface mActivityInterface;
 
     private final float mDragDistThreshold;
     private final float mFlingDistThreshold;
     private final long mTimeThreshold;
     private final int mAngleThreshold;
     private final float mSquaredSlop;
-    private final ISystemUiProxy mSysUiProxy;
     private final Context mContext;
     private final GestureDetector mGestureDetector;
 
-    public AssistantInputConsumer(Context context, ISystemUiProxy systemUiProxy,
-            ActivityControlHelper activityControlHelper, InputConsumer delegate,
-            InputMonitorCompat inputMonitor) {
+    public AssistantInputConsumer(Context context, GestureState gestureState,
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
         final Resources res = context.getResources();
         mContext = context;
-        mSysUiProxy = systemUiProxy;
         mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
         mFlingDistThreshold = res.getDimension(R.dimen.gestures_assistant_fling_threshold);
         mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
@@ -107,7 +104,7 @@
         float slop = ViewConfiguration.get(context).getScaledTouchSlop();
 
         mSquaredSlop = slop * slop;
-        mActivityControlHelper = activityControlHelper;
+        mActivityInterface = gestureState.getActivityInterface();
 
         mGestureDetector = new GestureDetector(context, new AssistantGestureListener());
     }
@@ -198,13 +195,7 @@
                         SWIPE_NOOP, mDirection, NAVBAR);
                     animator.addUpdateListener(valueAnimator -> {
                         float progress = (float) valueAnimator.getAnimatedValue();
-                        try {
-
-                            mSysUiProxy.onAssistantProgress(progress);
-                        } catch (RemoteException e) {
-                            Log.w(TAG, "Failed to send SysUI start/send assistant progress: "
-                                + progress, e);
-                        }
+                        SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(progress);
                     });
                     animator.setInterpolator(Interpolators.DEACCEL_2);
                     animator.start();
@@ -224,22 +215,17 @@
     private void updateAssistantProgress() {
         if (!mLaunchedAssistant) {
             mLastProgress = Math.min(mDistance * 1f / mDragDistThreshold, 1) * mTimeFraction;
-            try {
-                if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
-                    mSysUiProxy.onAssistantGestureCompletion(0);
-                    startAssistantInternal(SWIPE);
+            if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
+                SystemUiProxy.INSTANCE.get(mContext).onAssistantGestureCompletion(0);
+                startAssistantInternal(SWIPE);
 
-                    Bundle args = new Bundle();
-                    args.putInt(OPA_BUNDLE_TRIGGER, OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE);
-                    args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
-                    mSysUiProxy.startAssistant(args);
-                    mLaunchedAssistant = true;
-                } else {
-                    mSysUiProxy.onAssistantProgress(mLastProgress);
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to send SysUI start/send assistant progress: " + mLastProgress,
-                    e);
+                Bundle args = new Bundle();
+                args.putInt(OPA_BUNDLE_TRIGGER, OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE);
+                args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
+                SystemUiProxy.INSTANCE.get(mContext).startAssistant(args);
+                mLaunchedAssistant = true;
+            } else {
+                SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(mLastProgress);
             }
         }
     }
@@ -248,8 +234,7 @@
         UserEventDispatcher.newInstance(mContext)
             .logActionOnContainer(gestureType, mDirection, NAVBAR);
 
-        BaseDraggingActivity launcherActivity = mActivityControlHelper
-            .getCreatedActivity();
+        BaseDraggingActivity launcherActivity = mActivityInterface.getCreatedActivity();
         if (launcherActivity != null) {
             launcherActivity.getRootView().performHapticFeedback(
                 13, // HapticFeedbackConstants.GESTURE_END
@@ -274,24 +259,18 @@
         @Override
         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
             if (isValidAssistantGestureAngle(velocityX, -velocityY)
-                && mDistance >= mFlingDistThreshold
-                && !mLaunchedAssistant
-                && mState != STATE_DELEGATE_ACTIVE) {
+                    && mDistance >= mFlingDistThreshold
+                    && !mLaunchedAssistant
+                    && mState != STATE_DELEGATE_ACTIVE) {
                 mLastProgress = 1;
-                try {
-                    mSysUiProxy.onAssistantGestureCompletion(
-                        (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
-                    startAssistantInternal(FLING);
+                SystemUiProxy.INSTANCE.get(mContext).onAssistantGestureCompletion(
+                    (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
+                startAssistantInternal(FLING);
 
-                    Bundle args = new Bundle();
-                    args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
-                    mSysUiProxy.startAssistant(args);
-                    mLaunchedAssistant = true;
-                } catch (RemoteException e) {
-                    Log.w(TAG,
-                        "Failed to send SysUI start/send assistant progress: " + mLastProgress,
-                        e);
-                }
+                Bundle args = new Bundle();
+                args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
+                SystemUiProxy.INSTANCE.get(mContext).startAssistant(args);
+                mLaunchedAssistant = true;
             }
             return true;
         }
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 311ddd2..0b5129c 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
@@ -2,6 +2,7 @@
 
 import android.view.MotionEvent;
 
+import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 public abstract class DelegateInputConsumer implements InputConsumer {
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 25b4fe4..12b7c26 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
@@ -39,14 +39,16 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.DefaultDisplay;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.LockScreenRecentsActivity;
 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.util.AppWindowAnimationHelper;
-import com.android.quickstep.util.RecentsAnimationCallbacks;
-import com.android.quickstep.util.RecentsAnimationTargets;
-
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -74,6 +76,7 @@
 
     private final Context mContext;
     private final RecentsAnimationDeviceState mDeviceState;
+    private final GestureState mGestureState;
     private final float mTouchSlopSquared;
     private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
@@ -91,13 +94,15 @@
 
     private boolean mThresholdCrossed = false;
 
-    private RecentsAnimationTargets mTargetSet;
+    private RecentsAnimationController mRecentsAnimationController;
+    private RecentsAnimationTargets mRecentsAnimationTargets;
 
     public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
-            SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
-            int runningTaskId, int logId) {
+            GestureState gestureState, SwipeSharedState swipeSharedState,
+            InputMonitorCompat inputMonitorCompat, int runningTaskId, int logId) {
         mContext = context;
         mDeviceState = deviceState;
+        mGestureState = gestureState;
         mTouchSlopSquared = squaredTouchSlop(context);
         mSwipeSharedState = swipeSharedState;
         mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
@@ -202,9 +207,8 @@
 
     private void startRecentsTransition() {
         mThresholdCrossed = true;
-        RecentsAnimationCallbacks newListenerSet =
-                mSwipeSharedState.newRecentsAnimationListenerSet();
-        newListenerSet.addListener(this);
+        RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks();
+        callbacks.addListener(this);
         Intent intent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_DEFAULT)
                 .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class))
@@ -212,22 +216,24 @@
                 .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
 
         mInputMonitorCompat.pilferPointers();
-        startRecentsActivityAsync(intent, newListenerSet);
+        startRecentsActivityAsync(intent, callbacks);
     }
 
     @Override
-    public void onRecentsAnimationStart(RecentsAnimationTargets targetSet) {
-        mTargetSet = targetSet;
+    public void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        mRecentsAnimationController = controller;
+        mRecentsAnimationTargets = targets;
 
         Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
-        RemoteAnimationTargetCompat targetCompat = targetSet.findTask(mRunningTaskId);
+        RemoteAnimationTargetCompat targetCompat = targets.findTask(mRunningTaskId);
         if (targetCompat != null) {
             mAppWindowAnimationHelper.updateSource(displaySize, targetCompat);
         }
 
         Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN);
         displaySize.offsetTo(displaySize.left, 0);
-        mTransformParams.setTargetSet(mTargetSet)
+        mTransformParams.setTargetSet(mRecentsAnimationTargets)
                 .setLauncherOnTop(true);
         mAppWindowAnimationHelper.updateTargetRect(displaySize);
         mAppWindowAnimationHelper.applyTransform(mTransformParams);
@@ -237,12 +243,13 @@
 
     @Override
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        mTargetSet = null;
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
     }
 
     private void endRemoteAnimation() {
-        if (mTargetSet != null) {
-            mTargetSet.finishController(
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.finishController(
                     false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */);
         }
     }
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 59efb2d..370b487 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
@@ -39,17 +39,20 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.util.ObjectWrapper;
-import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
+import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseSwipeUpHandler;
+import com.android.quickstep.GestureState;
+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.util.RecentsAnimationTargets;
+import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -112,12 +115,13 @@
     private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
     private RunningWindowAnim mFinishAnimation;
 
-    public FallbackNoButtonInputConsumer(Context context,
+    public FallbackNoButtonInputConsumer(Context context, GestureState gestureState,
             OverviewComponentObserver overviewComponentObserver,
             RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
             InputConsumerController inputConsumer,
             boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
-        super(context, overviewComponentObserver, recentsModel, inputConsumer, runningTaskInfo.id);
+        super(context, gestureState, overviewComponentObserver, recentsModel, inputConsumer,
+                runningTaskInfo.id);
         mLauncherAlpha.value = 1;
 
         mRunningTaskInfo = runningTaskInfo;
@@ -157,7 +161,7 @@
     }
 
     private void onLauncherAlphaChanged() {
-        if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) {
+        if (mRecentsAnimationTargets != null && mEndTarget == null) {
             applyTransformUnchecked();
         }
     }
@@ -231,9 +235,11 @@
     @Override
     public void updateFinalShift() {
         mTransformParams.setProgress(mCurrentShift.value);
-        mRecentsAnimationWrapper.setWindowThresholdCrossed(!mInQuickSwitchMode
-                && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
-        if (mRecentsAnimationWrapper.targetSet != null) {
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.setWindowThresholdCrossed(!mInQuickSwitchMode
+                    && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
+        }
+        if (mRecentsAnimationTargets != null) {
             applyTransformUnchecked();
         }
     }
@@ -316,26 +322,25 @@
         switch (mEndTarget) {
             case HOME: {
                 if (mSwipeUpOverHome) {
-                    mRecentsAnimationWrapper.finish(false, null, false);
+                    mRecentsAnimationController.finish(false, null, false);
                     // Send a home intent to clear the task stack
                     mContext.startActivity(mOverviewComponentObserver.getHomeIntent());
                 } else {
-                    mRecentsAnimationWrapper.finish(true, null, true);
+                    mRecentsAnimationController.finish(true, null, true);
                 }
                 break;
             }
             case LAST_TASK:
-                mRecentsAnimationWrapper.finish(false, null, false);
+                mRecentsAnimationController.finish(false, null, false);
                 break;
             case RECENTS: {
                 if (mSwipeUpOverHome) {
-                    mRecentsAnimationWrapper.finish(true, null, true);
+                    mRecentsAnimationController.finish(true, null, true);
                     break;
                 }
 
-                ThumbnailData thumbnail =
-                        mRecentsAnimationWrapper.targetSet.controller.screenshotTask(mRunningTaskId);
-                mRecentsAnimationWrapper.setDeferCancelUntilNextTransition(true /* defer */,
+                ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(mRunningTaskId);
+                mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
                         false /* screenshot */);
 
                 ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
@@ -348,7 +353,7 @@
                 Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
                         .putExtras(extras);
                 mContext.startActivity(intent, options.toBundle());
-                mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
+                mRecentsAnimationController.cleanupScreenshot();
                 break;
             }
             case NEW_TASK: {
@@ -364,7 +369,7 @@
         if (mInQuickSwitchMode) {
             // Recalculate the end target, some views might have been initialized after
             // gesture has ended.
-            if (mRecentsView == null || !mRecentsAnimationWrapper.hasTargets()) {
+            if (mRecentsView == null || !hasTargets()) {
                 mEndTarget = LAST_TASK;
             } else {
                 final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
@@ -414,9 +419,10 @@
     }
 
     @Override
-    public void onRecentsAnimationStart(RecentsAnimationTargets targetSet) {
-        super.onRecentsAnimationStart(targetSet);
-        mRecentsAnimationWrapper.enableInputConsumer();
+    public void onRecentsAnimationStart(RecentsAnimationController controller,
+            RecentsAnimationTargets targets) {
+        super.onRecentsAnimationStart(controller, targets);
+        mRecentsAnimationController.enableInputConsumer();
 
         if (mRunningOverHome) {
             mAppWindowAnimationHelper.prepareAnimation(mDp, true);
@@ -428,7 +434,7 @@
 
     @Override
     public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        mRecentsAnimationWrapper.setController(null);
+        mRecentsView.setRecentsAnimationTargets(null, null);
         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 3e66d7b..fbedc0f 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
@@ -50,9 +50,11 @@
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.BaseSwipeUpHandler;
 import com.android.quickstep.BaseSwipeUpHandler.Factory;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.SwipeSharedState;
 import com.android.quickstep.SysUINavigationMode;
@@ -61,7 +63,7 @@
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -80,12 +82,13 @@
     public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3;
 
     private final RecentsAnimationDeviceState mDeviceState;
+    private final GestureState mGestureState;
     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 ActivityControlHelper mActivityControlHelper;
+    private final BaseActivityInterface mActivityInterface;
 
     private final BaseSwipeUpHandler.Factory mHandlerFactory;
 
@@ -125,20 +128,19 @@
     private int mLogId;
 
     public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
-            RunningTaskInfo runningTaskInfo, boolean isDeferredDownTarget,
-            Consumer<OtherActivityInputConsumer> onCompleteCallback,
+            GestureState gestureState, RunningTaskInfo runningTaskInfo,
+            boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
             SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
-            boolean disableHorizontalSwipe, ActivityControlHelper activityControlHelper,
-            Factory handlerFactory, int logId) {
+            boolean disableHorizontalSwipe, Factory handlerFactory, int logId) {
         super(base);
         mLogId = logId;
-
         mDeviceState = deviceState;
+        mGestureState = gestureState;
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         mRunningTask = runningTaskInfo;
         mMode = SysUINavigationMode.getMode(base);
         mHandlerFactory = handlerFactory;
-        mActivityControlHelper = activityControlHelper;
+        mActivityInterface = mGestureState.getActivityInterface();
 
         mMotionPauseDetector = new MotionPauseDetector(base);
         mMotionPauseMinDisplacement = base.getResources().getDimension(
@@ -317,7 +319,7 @@
         }
         mInputMonitorCompat.pilferPointers();
 
-        mActivityControlHelper.closeOverlay();
+        mActivityInterface.closeOverlay();
         ActivityManagerWrapper.getInstance().closeSystemWindows(
                 CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
 
@@ -330,8 +332,8 @@
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
 
         RecentsAnimationCallbacks listenerSet = mSwipeSharedState.getActiveListener();
-        final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mRunningTask, touchTimeMs,
-                listenerSet != null, isLikelyToStartNewTask);
+        final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mGestureState, mRunningTask,
+                touchTimeMs, listenerSet != null, isLikelyToStartNewTask);
 
         mInteractionHandler = handler;
         handler.setGestureEndCallback(this::onInteractionGestureFinished);
@@ -343,12 +345,11 @@
             mSwipeSharedState.applyActiveRecentsAnimationState(handler);
             notifyGestureStarted();
         } else {
-            RecentsAnimationCallbacks newListenerSet =
-                    mSwipeSharedState.newRecentsAnimationListenerSet();
-            newListenerSet.addListener(handler);
+            RecentsAnimationCallbacks callbacks = mSwipeSharedState.newRecentsAnimationCallbacks();
+            callbacks.addListener(handler);
             Intent intent = handler.getLaunchIntent();
             intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId);
-            startRecentsActivityAsync(intent, newListenerSet);
+            startRecentsActivityAsync(intent, callbacks);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 74e457c..c19754f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -26,7 +26,9 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -40,7 +42,7 @@
         implements InputConsumer {
 
     private final T mActivity;
-    private final ActivityControlHelper<T> mActivityControlHelper;
+    private final BaseActivityInterface<T> mActivityInterface;
     private final BaseDragLayer mTarget;
     private final InputMonitorCompat mInputMonitor;
 
@@ -51,13 +53,12 @@
     private final boolean mStartingInActivityBounds;
     private boolean mTargetHandledTouch;
 
-    public OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
-            boolean startingInActivityBounds,
-            ActivityControlHelper<T> activityControlHelper) {
+    public OverviewInputConsumer(GestureState gestureState, T activity,
+            @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
         mActivity = activity;
         mInputMonitor = inputMonitor;
         mStartingInActivityBounds = startingInActivityBounds;
-        mActivityControlHelper = activityControlHelper;
+        mActivityInterface = gestureState.getActivityInterface();
 
         mTarget = activity.getDragLayer();
         if (startingInActivityBounds) {
@@ -99,7 +100,7 @@
         if (!mTargetHandledTouch && handled) {
             mTargetHandledTouch = true;
             if (!mStartingInActivityBounds) {
-                mActivityControlHelper.closeOverlay();
+                mActivityInterface.closeOverlay();
                 ActivityManagerWrapper.getInstance()
                         .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
                 ActiveGestureLog.INSTANCE.addLog("startQuickstep");
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 03e0ae7..50069ea 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -35,7 +35,9 @@
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.GestureState;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -49,17 +51,17 @@
     private final float mSquaredTouchSlop;
     private final Context mContext;
     private final NavBarPosition mNavBarPosition;
-    private final ActivityControlHelper mActivityControlHelper;
+    private final BaseActivityInterface mActivityInterface;
 
     private boolean mInterceptedTouch;
     private VelocityTracker mVelocityTracker;
 
-    public OverviewWithoutFocusInputConsumer(Context context, InputMonitorCompat inputMonitor,
-            ActivityControlHelper activityControlHelper, boolean disableHorizontalSwipe) {
+    public OverviewWithoutFocusInputConsumer(Context context, GestureState gestureState,
+            InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
         mInputMonitor = inputMonitor;
         mDisableHorizontalSwipe = disableHorizontalSwipe;
         mContext = context;
-        mActivityControlHelper = activityControlHelper;
+        mActivityInterface = gestureState.getActivityInterface();
         mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
         mNavBarPosition = new NavBarPosition(context);
 
@@ -148,7 +150,7 @@
         }
 
         if (triggerQuickstep) {
-            mActivityControlHelper.closeOverlay();
+            mActivityInterface.closeOverlay();
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
             ActiveGestureLog.INSTANCE.addLog("startQuickstep");
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureTouchConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java
index 3101bb8..97ca730 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/QuickCaptureInputConsumer.java
@@ -35,18 +35,19 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
- * Touch consumer for handling events to launch quick capture from launcher
+ * Input consumer for handling events to launch quick capture from launcher
  * @param <T> Draggable activity subclass used by RecentsView
  */
-public class QuickCaptureTouchConsumer<T extends BaseDraggingActivity>
+public class QuickCaptureInputConsumer<T extends BaseDraggingActivity>
         extends DelegateInputConsumer {
 
-    private static final String TAG = "QuickCaptureTouchConsumer";
+    private static final String TAG = "QuickCaptureInputConsumer";
 
     private static final String QUICK_CAPTURE_PACKAGE = "com.google.auxe.compose";
     private static final String QUICK_CAPTURE_PACKAGE_DEV = "com.google.auxe.compose.debug";
@@ -72,15 +73,16 @@
 
     private RecentsView mRecentsView;
 
-    public QuickCaptureTouchConsumer(Context context, InputConsumer delegate,
-            InputMonitorCompat inputMonitor, ActivityControlHelper<T> activityControlHelper) {
+    public QuickCaptureInputConsumer(Context context, GestureState gestureState,
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
         mContext = context;
 
         float slop = ViewConfiguration.get(context).getScaledTouchSlop();
         mSquaredSlop = slop * slop;
 
-        activityControlHelper.createActivityInitListener(this::onActivityInit).register();
+        gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
+                .register();
     }
 
     @Override
@@ -88,7 +90,7 @@
         return TYPE_QUICK_CAPTURE | mDelegate.getType();
     }
 
-    private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+    private boolean onActivityInit(final BaseDraggingActivity activity, Boolean alreadyOnHome) {
         mRecentsView = activity.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 8eede81..e04c0c7 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
@@ -17,6 +17,7 @@
 
 import android.view.MotionEvent;
 
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.SwipeSharedState;
 
 /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
index a0e20f2..d5ed321 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -16,16 +16,15 @@
 package com.android.quickstep.inputconsumers;
 
 import android.content.Context;
-import android.os.RemoteException;
-import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.quickstep.SystemUiProxy;
 
 /**
  * An input consumer that detects swipe up and hold to exit screen pinning mode.
@@ -39,25 +38,21 @@
 
     private float mTouchDownY;
 
-    public ScreenPinnedInputConsumer(Context context, ISystemUiProxy sysuiProxy,
-            ActivityControlHelper activityControl) {
+    public ScreenPinnedInputConsumer(Context context, GestureState gestureState) {
         mMotionPauseMinDisplacement = context.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
         mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
         mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
             if (isPaused) {
-                try {
-                    sysuiProxy.stopScreenPinning();
-                    BaseDraggingActivity launcherActivity = activityControl.getCreatedActivity();
-                    if (launcherActivity != null) {
-                        launcherActivity.getRootView().performHapticFeedback(
-                                HapticFeedbackConstants.LONG_PRESS,
-                                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                    }
-                    mMotionPauseDetector.clear();
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Unable to stop screen pinning ", e);
+                SystemUiProxy.INSTANCE.get(context).stopScreenPinning();
+                BaseDraggingActivity launcherActivity = gestureState.getActivityInterface()
+                        .getCreatedActivity();
+                if (launcherActivity != null) {
+                    launcherActivity.getRootView().performHapticFeedback(
+                            HapticFeedbackConstants.LONG_PRESS,
+                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 }
+                mMotionPauseDetector.clear();
             }
         });
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index dddfc8d..5eee897 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -28,7 +28,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
-import android.os.RemoteException;
 
 import androidx.annotation.Nullable;
 
@@ -36,13 +35,13 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.SystemUiProxy;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.utilities.RectFEvaluator;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -337,14 +336,10 @@
     }
 
     private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
-        ISystemUiProxy sysUiProxy = RecentsModel.INSTANCE.get(activity).getSystemUiProxy();
-        if (sysUiProxy != null) {
-            try {
-                mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds());
-                return;
-            } catch (RemoteException e) {
-                // Use half screen size
-            }
+        SystemUiProxy proxy = SystemUiProxy.INSTANCE.get(activity);
+        if (proxy.isActive()) {
+            mSourceStackBounds.set(proxy.getNonMinimizedSplitScreenSecondaryBounds());
+            return;
         }
 
         // Assume that the task size is half screen size (minus the insets and the divider size)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationTargets.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationTargets.java
deleted file mode 100644
index 187a404..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationTargets.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-
-import android.graphics.Rect;
-
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-import java.util.function.Consumer;
-
-/**
- * Extension of {@link RemoteAnimationTargets} with additional information about swipe
- * up animation
- */
-public class RecentsAnimationTargets extends RemoteAnimationTargets {
-
-    private final boolean mShouldMinimizeSplitScreen;
-    private final Consumer<RecentsAnimationTargets> mOnFinishListener;
-
-    public final RecentsAnimationControllerCompat controller;
-    public final Rect homeContentInsets;
-    public final Rect minimizedHomeBounds;
-
-    public RecentsAnimationTargets(RecentsAnimationControllerCompat controller,
-            RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpapers,
-            Rect homeContentInsets, Rect minimizedHomeBounds, boolean shouldMinimizeSplitScreen,
-            Consumer<RecentsAnimationTargets> onFinishListener) {
-        super(apps, wallpapers, MODE_CLOSING);
-        this.controller = controller;
-        this.homeContentInsets = homeContentInsets;
-        this.minimizedHomeBounds = minimizedHomeBounds;
-        this.mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
-        this.mOnFinishListener = onFinishListener;
-    }
-
-    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(controller, new RemoteAnimationTargetCompat[0],
-                new RemoteAnimationTargetCompat[0], homeContentInsets, minimizedHomeBounds,
-                mShouldMinimizeSplitScreen, mOnFinishListener);
-    }
-
-    public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
-        mOnFinishListener.accept(this);
-        UI_HELPER_EXECUTOR.execute(() -> {
-            controller.setInputConsumerEnabled(false);
-            controller.finish(toRecents, sendUserLeaveHint);
-
-            if (callback != null) {
-                MAIN_EXECUTOR.execute(callback);
-            }
-        });
-    }
-
-    public void enableInputConsumer() {
-        UI_HELPER_EXECUTOR.submit(() -> {
-            controller.hideCurrentInputMethod();
-            controller.setInputConsumerEnabled(true);
-        });
-    }
-
-    public void setWindowThresholdCrossed(boolean thresholdCrossed) {
-        UI_HELPER_EXECUTOR.execute(() -> {
-            controller.setAnimationTargetsBehindSystemBars(!thresholdCrossed);
-            if (mShouldMinimizeSplitScreen && thresholdCrossed) {
-                // NOTE: As a workaround for conflicting animations (Launcher animating the task
-                // leash, and SystemUI resizing the docked stack, which resizes the task), we
-                // currently only set the minimized mode, and not the inverse.
-                // TODO: Synchronize the minimize animation with the launcher animation
-                controller.setSplitScreenMinimized(thresholdCrossed);
-            }
-        });
-    }
-
-    public ThumbnailData screenshotTask(int taskId) {
-        return controller != null ? controller.screenshotTask(taskId) : null;
-    }
-
-    public void cancelAnimation() {
-        finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
-    }
-
-    public void finishAnimation() {
-        finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
-    }
-}
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 0f9184f..764e0f3 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
@@ -220,8 +220,8 @@
     @Override
     public AppWindowAnimationHelper.TransformParams getLiveTileParams(
             boolean mightNeedToRefill) {
-        if (!mEnableDrawingLiveTile || mRecentsAnimationWrapper == null
-                || mAppWindowAnimationHelper == null) {
+        if (!mEnableDrawingLiveTile || mRecentsAnimationController == null
+                || mRecentsAnimationTargets == null || mAppWindowAnimationHelper == null) {
             return null;
         }
         TaskView taskView = getRunningTaskView();
@@ -245,7 +245,7 @@
             mTransformParams.setProgress(1f)
                     .setCurrentRectAndTargetAlpha(mTempRectF, taskView.getAlpha())
                     .setSyncTransactionApplier(mSyncTransactionApplier)
-                    .setTargetSet(mRecentsAnimationWrapper.targetSet)
+                    .setTargetSet(mRecentsAnimationTargets)
                     .setLauncherOnTop(true);
         }
         return mTransformParams;
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 cefe264..ea06991 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
@@ -101,13 +101,14 @@
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
-import com.android.quickstep.RecentsAnimationWrapper;
+import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.ViewUtils;
 import com.android.quickstep.util.AppWindowAnimationHelper;
+import com.android.quickstep.RecentsAnimationTargets;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -155,7 +156,8 @@
                 }
             };
 
-    protected RecentsAnimationWrapper mRecentsAnimationWrapper;
+    protected RecentsAnimationController mRecentsAnimationController;
+    protected RecentsAnimationTargets mRecentsAnimationTargets;
     protected AppWindowAnimationHelper mAppWindowAnimationHelper;
     protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
     protected int mTaskWidth;
@@ -807,7 +809,8 @@
         mIgnoreResetTaskId = -1;
         mTaskListChangeId = -1;
 
-        mRecentsAnimationWrapper = null;
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
         mAppWindowAnimationHelper = null;
 
         unloadVisibleTaskData();
@@ -1692,10 +1695,14 @@
 
     public void redrawLiveTile(boolean mightNeedToRefill) { }
 
-    public void setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper) {
-        mRecentsAnimationWrapper = recentsAnimationWrapper;
+    // TODO: To be removed in a follow up CL
+    public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
+            RecentsAnimationTargets recentsAnimationTargets) {
+        mRecentsAnimationController = recentsAnimationController;
+        mRecentsAnimationTargets = recentsAnimationTargets;
     }
 
+    // TODO: To be removed in a follow up CL
     public void setAppWindowAnimationHelper(AppWindowAnimationHelper appWindowAnimationHelper) {
         mAppWindowAnimationHelper = appWindowAnimationHelper;
     }
@@ -1711,14 +1718,14 @@
     }
 
     public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
-        if (mRecentsAnimationWrapper == null) {
+        if (mRecentsAnimationController == null) {
             if (onFinishComplete != null) {
                 onFinishComplete.run();
             }
             return;
         }
 
-        mRecentsAnimationWrapper.finish(toRecents, onFinishComplete);
+        mRecentsAnimationController.finish(toRecents, onFinishComplete);
     }
 
     public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index af75364..d4db05a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -71,7 +71,7 @@
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.RemoteAnimationTargets;
+import com.android.quickstep.RemoteAnimationTargets;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index 693ae60..aa0dfc3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -24,18 +24,18 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SystemUiProxy;
 
 public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
 
     private static final String TAG = "BackButtonAlphaHandler";
 
     private final Launcher mLauncher;
-    private final OverviewInteractionState mOverviewInteractionState;
 
     public BackButtonAlphaHandler(Launcher launcher) {
         mLauncher = launcher;
-        mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(mLauncher);
     }
 
     @Override
@@ -49,14 +49,23 @@
         if (!config.playNonAtomicComponent()) {
             return;
         }
-        float fromAlpha = mOverviewInteractionState.getBackButtonAlpha();
+
+        if (!SysUINavigationMode.getMode(mLauncher).hasGestures) {
+            // If the nav mode is not gestural, then force back button alpha to be 1
+            UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA, 1f,
+                    true /* animate */);
+            return;
+        }
+
+        float fromAlpha = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
         float toAlpha = toState.hideBackButton ? 0 : 1;
         if (Float.compare(fromAlpha, toAlpha) != 0) {
             ValueAnimator anim = ValueAnimator.ofFloat(fromAlpha, toAlpha);
             anim.setDuration(config.duration);
             anim.addUpdateListener(valueAnimator -> {
                 final float alpha = (float) valueAnimator.getAnimatedValue();
-                mOverviewInteractionState.setBackButtonAlpha(alpha, false);
+                UiThreadHelper.setBackButtonAlphaAsync(mLauncher, UiFactory.SET_BACK_BUTTON_ALPHA,
+                        alpha, false /* animate */);
             });
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index b0b5dcf..17c681b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -51,11 +51,12 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.proxy.ProxyActivityStarter;
 import com.android.launcher3.proxy.StartActivityParams;
-import com.android.quickstep.OverviewInteractionState;
+import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.systemui.shared.system.ActivityCompat;
 
@@ -65,6 +66,15 @@
 
 public class UiFactory extends RecentsUiFactory {
 
+    /**
+     * Reusable command for applying the back button alpha on the background thread.
+     */
+    public static final UiThreadHelper.AsyncCommand SET_BACK_BUTTON_ALPHA =
+            (context, arg1, arg2) -> {
+        SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(Float.intBitsToFloat(arg1),
+                arg2 != 0);
+    };
+
     public static Runnable enableLiveUIChanges(Launcher launcher) {
         NavigationModeChangeListener listener = m -> {
             launcher.getDragLayer().recreateControllers();
@@ -88,7 +98,9 @@
      * Sets the back button visibility based on the current state/window focus.
      */
     public static void onLauncherStateOrFocusChanged(Launcher launcher) {
-        boolean shouldBackButtonBeHidden = launcher != null
+        Mode mode = SysUINavigationMode.getMode(launcher);
+        boolean shouldBackButtonBeHidden = mode.hasGestures
+                && launcher != null
                 && launcher.getStateManager().getState().hideBackButton
                 && launcher.hasWindowFocus();
         if (shouldBackButtonBeHidden) {
@@ -96,8 +108,8 @@
             shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher,
                     TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
         }
-        OverviewInteractionState.INSTANCE.get(launcher)
-                .setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
+        UiThreadHelper.setBackButtonAlphaAsync(launcher, UiFactory.SET_BACK_BUTTON_ALPHA,
+                shouldBackButtonBeHidden ? 0f : 1f, true /* animate */);
         if (launcher != null && launcher.getDragLayer() != null) {
             launcher.getRootView().setDisallowBackGesture(shouldBackButtonBeHidden);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 9813295..ef6a5e2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -47,8 +47,8 @@
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.OverviewInteractionState;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.LayoutUtils;
 
@@ -137,8 +137,7 @@
         } else if (fromState == OVERVIEW) {
             return isDragTowardPositive ? ALL_APPS : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
-            int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher)
-                    .getSystemUiStateFlags();
+            int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
             return mAllowDragToOverview && TouchInteractionService.isConnected()
                     && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0
                     ? OVERVIEW : ALL_APPS;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index 11a8043..16bd9ed 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -21,8 +21,6 @@
 import static android.view.MotionEvent.ACTION_CANCEL;
 
 import android.graphics.PointF;
-import android.os.RemoteException;
-import android.util.Log;
 import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
@@ -37,9 +35,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
-import com.android.quickstep.RecentsModel;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 
+import com.android.quickstep.SystemUiProxy;
 import java.io.PrintWriter;
 
 /**
@@ -62,9 +59,9 @@
      */
     private static final int FLAG_SLIPPERY = 0x20000000;
 
-    protected final Launcher mLauncher;
+    private final Launcher mLauncher;
+    private final SystemUiProxy mSystemUiProxy;
     private final float mTouchSlop;
-    private ISystemUiProxy mSysUiProxy;
     private int mLastAction;
     private final SparseArray<PointF> mDownEvents;
 
@@ -73,6 +70,7 @@
 
     public StatusBarTouchController(Launcher l) {
         mLauncher = l;
+        mSystemUiProxy = SystemUiProxy.INSTANCE.get(mLauncher);
         // Guard against TAPs by increasing the touch slop.
         mTouchSlop = 2 * ViewConfiguration.get(l).getScaledTouchSlop();
         mDownEvents = new SparseArray<>();
@@ -82,17 +80,14 @@
     public void dump(String prefix, PrintWriter writer) {
         writer.println(prefix + "mCanIntercept:" + mCanIntercept);
         writer.println(prefix + "mLastAction:" + MotionEvent.actionToString(mLastAction));
-        writer.println(prefix + "mSysUiProxy available:" + (mSysUiProxy != null));
+        writer.println(prefix + "mSysUiProxy available:"
+                + SystemUiProxy.INSTANCE.get(mLauncher).isActive());
     }
 
     private void dispatchTouchEvent(MotionEvent ev) {
-        try {
-            if (mSysUiProxy != null) {
-                mLastAction = ev.getActionMasked();
-                mSysUiProxy.onStatusBarMotionEvent(ev);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Remote exception on sysUiProxy.", e);
+        if (mSystemUiProxy.isActive()) {
+            mLastAction = ev.getActionMasked();
+            mSystemUiProxy.onStatusBarMotionEvent(ev);
         }
     }
 
@@ -170,7 +165,6 @@
                 return false;
             }
         }
-        mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy();
-        return mSysUiProxy != null;
+        return SystemUiProxy.INSTANCE.get(mLauncher).isActive();
     }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
similarity index 96%
rename from quickstep/src/com/android/quickstep/ActivityControlHelper.java
rename to quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 132d6ab..409bec6 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -32,7 +32,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RemoteAnimationTargets;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -43,7 +42,7 @@
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
  */
 @TargetApi(Build.VERSION_CODES.P)
-public interface ActivityControlHelper<T extends BaseDraggingActivity> {
+public interface BaseActivityInterface<T extends BaseDraggingActivity> {
 
     void onTransitionCancelled(T activity, boolean activityVisible);
 
@@ -113,7 +112,7 @@
 
         default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
 
-        void createActivityController(long transitionLength);
+        void createActivityInterface(long transitionLength);
 
         default void adjustActivityControllerInterpolators() { }
 
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
new file mode 100644
index 0000000..de64227
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.launcher3.BaseDraggingActivity;
+
+/**
+ * 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 {
+
+    // Needed to interact with the current activity
+    private BaseActivityInterface mActivityInterface;
+
+    public GestureState(BaseActivityInterface activityInterface) {
+        mActivityInterface = activityInterface;
+    }
+
+    public <T extends BaseDraggingActivity> BaseActivityInterface<T> getActivityInterface() {
+        return mActivityInterface;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
rename to quickstep/src/com/android/quickstep/InputConsumer.java
index 045bafe..62c0ded 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep.inputconsumers;
+package com.android.quickstep;
 
 import android.annotation.TargetApi;
 import android.os.Build;
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 331183a..73b78db 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -62,7 +62,7 @@
     private final Intent mFallbackIntent;
     private final SparseIntArray mConfigChangesMap = new SparseIntArray();
     private String mUpdateRegisteredPackage;
-    private ActivityControlHelper mActivityControlHelper;
+    private BaseActivityInterface mActivityInterface;
     private Intent mOverviewIntent;
     private boolean mIsHomeAndOverviewSame;
     private boolean mIsDefaultHome;
@@ -106,7 +106,7 @@
     }
 
     /**
-     * Update overview intent and {@link ActivityControlHelper} based off the current launcher home
+     * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
      * component.
      */
     private void updateOverviewTargets() {
@@ -119,13 +119,13 @@
         // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
         // launcher made invisible become visible again before the new activity control helper
         // becomes active.
-        if (mActivityControlHelper != null) {
-            mActivityControlHelper.onAssistantVisibilityChanged(0.f);
+        if (mActivityInterface != null) {
+            mActivityInterface.onAssistantVisibilityChanged(0.f);
         }
 
         if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
-            mActivityControlHelper = new LauncherActivityControllerHelper();
+            mActivityInterface = new LauncherActivityInterface();
             mIsHomeAndOverviewSame = true;
             mOverviewIntent = mMyHomeIntent;
             mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
@@ -138,7 +138,7 @@
         } else {
             // The default home app is a different launcher. Use the fallback Overview instead.
 
-            mActivityControlHelper = new FallbackActivityControllerHelper();
+            mActivityInterface = new FallbackActivityInterface();
             mIsHomeAndOverviewSame = false;
             mOverviewIntent = mFallbackIntent;
             mCurrentHomeIntent.setComponent(defaultHome);
@@ -230,7 +230,7 @@
      *
      * @return the current activity control helper
      */
-    public ActivityControlHelper getActivityControlHelper() {
-        return mActivityControlHelper;
+    public BaseActivityInterface getActivityInterface() {
+        return mActivityInterface;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
deleted file mode 100644
index 17462ab..0000000
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2018 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.UI_HELPER_EXECUTOR;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.ISystemUiProxy;
-
-/**
- * Sets alpha for the back button
- */
-public class OverviewInteractionState {
-
-    private static final String TAG = "OverviewFlags";
-
-    private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
-
-    // We do not need any synchronization for this variable as its only written on UI thread.
-    public static final MainThreadInitializedObject<OverviewInteractionState> INSTANCE =
-            new MainThreadInitializedObject<>(OverviewInteractionState::new);
-
-    private static final int MSG_SET_PROXY = 200;
-    private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
-
-    private final Context mContext;
-    private final Handler mUiHandler;
-    private final Handler mBgHandler;
-
-    // These are updated on the background thread
-    private ISystemUiProxy mISystemUiProxy;
-    private float mBackButtonAlpha = 1;
-
-    private int mSystemUiStateFlags;
-
-    private OverviewInteractionState(Context context) {
-        mContext = context;
-
-        // Data posted to the uihandler will be sent to the bghandler. Data is sent to uihandler
-        // because of its high send frequency and data may be very different than the previous value
-        // For example, send back alpha on uihandler to avoid flickering when setting its visibility
-        mUiHandler = new Handler(this::handleUiMessage);
-        mBgHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleBgMessage);
-
-        onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context)
-                .addModeChangeListener(this::onNavigationModeChanged));
-    }
-
-    public float getBackButtonAlpha() {
-        return mBackButtonAlpha;
-    }
-
-    public void setBackButtonAlpha(float alpha, boolean animate) {
-        if (!modeSupportsGestures()) {
-            alpha = 1;
-        }
-        mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_ALPHA);
-        mUiHandler.obtainMessage(MSG_SET_BACK_BUTTON_ALPHA, animate ? 1 : 0, 0, alpha)
-                .sendToTarget();
-    }
-
-    public void setSystemUiProxy(ISystemUiProxy proxy) {
-        mBgHandler.obtainMessage(MSG_SET_PROXY, proxy).sendToTarget();
-    }
-
-    // TODO(141886704): See if we can remove this
-    public void setSystemUiStateFlags(int stateFlags) {
-        mSystemUiStateFlags = stateFlags;
-    }
-
-    public int getSystemUiStateFlags() {
-        return mSystemUiStateFlags;
-    }
-
-    private boolean handleUiMessage(Message msg) {
-        if (msg.what == MSG_SET_BACK_BUTTON_ALPHA) {
-            mBackButtonAlpha = (float) msg.obj;
-        }
-        mBgHandler.obtainMessage(msg.what, msg.arg1, msg.arg2, msg.obj).sendToTarget();
-        return true;
-    }
-
-    private boolean handleBgMessage(Message msg) {
-        switch (msg.what) {
-            case MSG_SET_PROXY:
-                mISystemUiProxy = (ISystemUiProxy) msg.obj;
-                break;
-            case MSG_SET_BACK_BUTTON_ALPHA:
-                applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1);
-                return true;
-        }
-        return true;
-    }
-
-    @WorkerThread
-    private void applyBackButtonAlpha(float alpha, boolean animate) {
-        if (mISystemUiProxy == null) {
-            return;
-        }
-        try {
-            mISystemUiProxy.setBackButtonAlpha(alpha, animate);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Unable to update overview back button alpha", e);
-        }
-    }
-
-    private void onNavigationModeChanged(SysUINavigationMode.Mode mode) {
-        resetHomeBounceSeenOnQuickstepEnabledFirstTime();
-    }
-
-    private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
-        if (modeSupportsGestures() && !Utilities.getPrefs(mContext).getBoolean(
-                HAS_ENABLED_QUICKSTEP_ONCE, true)) {
-            Utilities.getPrefs(mContext).edit()
-                .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
-                .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
-                .apply();
-        }
-    }
-
-    private boolean modeSupportsGestures() {
-        return SysUINavigationMode.getMode(mContext).hasGestures;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
similarity index 73%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationCallbacks.java
rename to quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 415f767..2918879 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep.util;
+package com.android.quickstep;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -25,34 +25,29 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.TouchInteractionService;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.Set;
-import java.util.function.Consumer;
 
 /**
- * Wrapper around {@link com.android.systemui.shared.system.RecentsAnimationListener} which delegates callbacks to multiple listeners
- * on the main thread
+ * Wrapper around {@link com.android.systemui.shared.system.RecentsAnimationListener} which
+ * delegates callbacks to multiple listeners on the main thread
  */
 public class RecentsAnimationCallbacks implements
         com.android.systemui.shared.system.RecentsAnimationListener {
 
     private final Set<RecentsAnimationListener> mListeners = new ArraySet<>();
     private final boolean mShouldMinimizeSplitScreen;
-    private final Consumer<RecentsAnimationTargets> mOnFinishListener;
-    private RecentsAnimationControllerCompat mController;
+
+    // TODO(141886704): Remove these references when they are no longer needed
+    private RecentsAnimationController mController;
 
     private boolean mCancelled;
 
-    public RecentsAnimationCallbacks(boolean shouldMinimizeSplitScreen,
-            Consumer<RecentsAnimationTargets> onFinishListener) {
+    public RecentsAnimationCallbacks(boolean shouldMinimizeSplitScreen) {
         mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
-        mOnFinishListener = onFinishListener;
-        TouchInteractionService.getSwipeSharedState().setRecentsAnimationCanceledCallback(
-                () -> mController.cleanupScreenshot());
     }
 
     @UiThread
@@ -67,26 +62,9 @@
         mListeners.remove(listener);
     }
 
-    // Called only in R+ platform
-    @BinderThread
-    public final void onAnimationStart(RecentsAnimationControllerCompat controller,
-            RemoteAnimationTargetCompat[] appTargets,
-            RemoteAnimationTargetCompat[] wallpaperTargets,
-            Rect homeContentInsets, Rect minimizedHomeBounds) {
-        mController = controller;
-        RecentsAnimationTargets targetSet = new RecentsAnimationTargets(controller, appTargets,
-                wallpaperTargets, homeContentInsets, minimizedHomeBounds,
-                mShouldMinimizeSplitScreen, mOnFinishListener);
-
-        if (mCancelled) {
-            targetSet.cancelAnimation();
-        } else {
-            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
-                for (RecentsAnimationListener listener : getListeners()) {
-                    listener.onRecentsAnimationStart(targetSet);
-                }
-            });
-        }
+    public void notifyAnimationCanceled() {
+        mCancelled = true;
+        onAnimationCanceled(null);
     }
 
     // Called only in Q platform
@@ -99,6 +77,29 @@
                 homeContentInsets, minimizedHomeBounds);
     }
 
+    // Called only in R+ platform
+    @BinderThread
+    public final void onAnimationStart(RecentsAnimationControllerCompat animationController,
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets,
+            Rect homeContentInsets, Rect minimizedHomeBounds) {
+        RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
+                wallpaperTargets, homeContentInsets, minimizedHomeBounds);
+        mController = new RecentsAnimationController(animationController,
+                mShouldMinimizeSplitScreen, this::onAnimationFinished);
+
+        if (mCancelled) {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(),
+                    mController::finishAnimationToApp);
+        } else {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+                for (RecentsAnimationListener listener : getListeners()) {
+                    listener.onRecentsAnimationStart(mController, targets);
+                }
+            });
+        }
+    }
+
     @BinderThread
     @Override
     public final void onAnimationCanceled(ThumbnailData thumbnailData) {
@@ -109,25 +110,31 @@
         });
     }
 
-    private RecentsAnimationListener[] getListeners() {
-        return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]);
+    private final void onAnimationFinished(RecentsAnimationController controller) {
+        Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+            for (RecentsAnimationListener listener : getListeners()) {
+                listener.onRecentsAnimationFinished(controller);
+            }
+        });
     }
 
-    public void cancelListener() {
-        mCancelled = true;
-        onAnimationCanceled(null);
+    private RecentsAnimationListener[] getListeners() {
+        return mListeners.toArray(new RecentsAnimationListener[mListeners.size()]);
     }
 
     /**
      * Listener for the recents animation callbacks.
      */
     public interface RecentsAnimationListener {
-        void onRecentsAnimationStart(RecentsAnimationTargets targetSet);
+        default void onRecentsAnimationStart(RecentsAnimationController controller,
+                RecentsAnimationTargets targetSet) {}
 
         /**
          * Callback from the system when the recents animation is canceled. {@param thumbnailData}
          * is passed back for rendering screenshot to replace live tile.
          */
-        void onRecentsAnimationCanceled(ThumbnailData thumbnailData);
+        default void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {}
+
+        default void onRecentsAnimationFinished(RecentsAnimationController controller) {}
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
new file mode 100644
index 0000000..d938dc5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.util.Preconditions;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Wrapper around RecentsAnimationController to help with some synchronization
+ */
+public class RecentsAnimationController {
+
+    private static final String TAG = "RecentsAnimationController";
+
+    private final RecentsAnimationControllerCompat mController;
+    private final Consumer<RecentsAnimationController> mOnFinishedListener;
+    private final boolean mShouldMinimizeSplitScreen;
+
+    private boolean mWindowThresholdCrossed = false;
+
+    private InputConsumerController mInputConsumerController;
+    private Supplier<InputConsumer> mInputProxySupplier;
+    private InputConsumer mInputConsumer;
+    private boolean mTouchInProgress;
+    private boolean mFinishPending;
+
+    public RecentsAnimationController(RecentsAnimationControllerCompat controller,
+            boolean shouldMinimizeSplitScreen,
+            Consumer<RecentsAnimationController> onFinishedListener) {
+        mController = controller;
+        mOnFinishedListener = onFinishedListener;
+        mShouldMinimizeSplitScreen = shouldMinimizeSplitScreen;
+
+        setWindowThresholdCrossed(mWindowThresholdCrossed);
+    }
+
+    /**
+     * Synchronously takes a screenshot of the task with the given {@param taskId} if the task is
+     * currently being animated.
+     */
+    public ThumbnailData screenshotTask(int taskId) {
+        return mController != null ? mController.screenshotTask(taskId) : null;
+    }
+
+    /**
+     * Indicates that the gesture has crossed the window boundary threshold and system UI can be
+     * update the represent the window behind
+     */
+    public void setWindowThresholdCrossed(boolean windowThresholdCrossed) {
+        if (mWindowThresholdCrossed != windowThresholdCrossed) {
+            mWindowThresholdCrossed = windowThresholdCrossed;
+            UI_HELPER_EXECUTOR.execute(() -> {
+                mController.setAnimationTargetsBehindSystemBars(!windowThresholdCrossed);
+                if (mShouldMinimizeSplitScreen && windowThresholdCrossed) {
+                    // NOTE: As a workaround for conflicting animations (Launcher animating the task
+                    // leash, and SystemUI resizing the docked stack, which resizes the task), we
+                    // currently only set the minimized mode, and not the inverse.
+                    // TODO: Synchronize the minimize animation with the launcher animation
+                    mController.setSplitScreenMinimized(windowThresholdCrossed);
+                }
+            });
+        }
+    }
+
+    /**
+     * Notifies the controller that we want to defer cancel until the next app transition starts.
+     * If {@param screenshot} is set, then we will receive a screenshot on the next
+     * {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)} and we must also call
+     * {@link #cleanupScreenshot()} when that screenshot is no longer used.
+     */
+    public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
+        mController.setDeferCancelUntilNextTransition(defer, screenshot);
+    }
+
+    /**
+     * Cleans up the screenshot previously returned from
+     * {@link RecentsAnimationCallbacks#onAnimationCanceled(ThumbnailData)}.
+     */
+    public void cleanupScreenshot() {
+        UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot());
+    }
+
+    @UiThread
+    public void finishAnimationToHome() {
+        finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+    }
+
+    @UiThread
+    public void finishAnimationToApp() {
+        finishAndClear(false /* toRecents */, null, false /* sendUserLeaveHint */);
+    }
+
+    /** See {@link #finish(boolean, Runnable, boolean)} */
+    @UiThread
+    public void finish(boolean toRecents, Runnable onFinishComplete) {
+        finish(toRecents, onFinishComplete, false /* sendUserLeaveHint */);
+    }
+
+    /**
+     * @param onFinishComplete A callback that runs on the main thread after the animation
+     *                         controller has finished on the background thread.
+     * @param sendUserLeaveHint Determines whether userLeaveHint flag will be set on the pausing
+     *                          activity. If userLeaveHint is true, the activity will enter into
+     *                          picture-in-picture mode upon being paused.
+     */
+    @UiThread
+    public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
+        Preconditions.assertUIThread();
+        if (!toRecents) {
+            finishAndClear(false, onFinishComplete, sendUserLeaveHint);
+        } else {
+            if (mTouchInProgress) {
+                mFinishPending = true;
+                // Execute the callback
+                if (onFinishComplete != null) {
+                    onFinishComplete.run();
+                }
+            } else {
+                finishAndClear(true, onFinishComplete, sendUserLeaveHint);
+            }
+        }
+    }
+
+    private void finishAndClear(boolean toRecents, Runnable onFinishComplete,
+            boolean sendUserLeaveHint) {
+        disableInputProxy();
+        finishController(toRecents, onFinishComplete, sendUserLeaveHint);
+    }
+
+    @UiThread
+    public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
+        mOnFinishedListener.accept(this);
+        UI_HELPER_EXECUTOR.execute(() -> {
+            mController.setInputConsumerEnabled(false);
+            mController.finish(toRecents, sendUserLeaveHint);
+            if (callback != null) {
+                MAIN_EXECUTOR.execute(callback);
+            }
+        });
+    }
+
+    /**
+     * Enables the input consumer to start intercepting touches in the app window.
+     */
+    public void enableInputConsumer() {
+        UI_HELPER_EXECUTOR.submit(() -> {
+            mController.hideCurrentInputMethod();
+            mController.setInputConsumerEnabled(true);
+        });
+    }
+
+    public void enableInputProxy(InputConsumerController inputConsumerController,
+            Supplier<InputConsumer> inputProxySupplier) {
+        mInputProxySupplier = inputProxySupplier;
+        mInputConsumerController = inputConsumerController;
+        mInputConsumerController.setInputListener(this::onInputConsumerEvent);
+    }
+
+    private void disableInputProxy() {
+        if (mInputConsumer != null && mTouchInProgress) {
+            long now = SystemClock.uptimeMillis();
+            MotionEvent dummyCancel = MotionEvent.obtain(now,  now, ACTION_CANCEL, 0, 0, 0);
+            mInputConsumer.onMotionEvent(dummyCancel);
+            dummyCancel.recycle();
+        }
+        if (mInputConsumerController != null) {
+            mInputConsumerController.setInputListener(null);
+        }
+    }
+
+    private boolean onInputConsumerEvent(InputEvent ev) {
+        if (ev instanceof MotionEvent) {
+            onInputConsumerMotionEvent((MotionEvent) ev);
+        } else if (ev instanceof KeyEvent) {
+            if (mInputConsumer == null) {
+                mInputConsumer = mInputProxySupplier.get();
+            }
+            mInputConsumer.onKeyEvent((KeyEvent) ev);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean onInputConsumerMotionEvent(MotionEvent ev) {
+        int action = ev.getAction();
+
+        // Just to be safe, verify that ACTION_DOWN comes before any other action,
+        // and ignore any ACTION_DOWN after the first one (though that should not happen).
+        if (!mTouchInProgress && action != ACTION_DOWN) {
+            Log.w(TAG, "Received non-down motion before down motion: " + action);
+            return false;
+        }
+        if (mTouchInProgress && action == ACTION_DOWN) {
+            Log.w(TAG, "Received down motion while touch was already in progress");
+            return false;
+        }
+
+        if (action == ACTION_DOWN) {
+            mTouchInProgress = true;
+            if (mInputConsumer == null) {
+                mInputConsumer = mInputProxySupplier.get();
+            }
+        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
+            // Finish any pending actions
+            mTouchInProgress = false;
+            if (mFinishPending) {
+                mFinishPending = false;
+                finishAndClear(true /* toRecents */, null, false /* sendUserLeaveHint */);
+            }
+        }
+        if (mInputConsumer != null) {
+            mInputConsumer.onMotionEvent(ev);
+        }
+
+        return true;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
new file mode 100644
index 0000000..9353759
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -0,0 +1,53 @@
+/*
+ * 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.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.graphics.Rect;
+
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Extension of {@link RemoteAnimationTargets} with additional information about swipe
+ * up animation
+ */
+public class RecentsAnimationTargets extends RemoteAnimationTargets {
+
+    public final Rect homeContentInsets;
+    public final Rect minimizedHomeBounds;
+
+    public RecentsAnimationTargets(RemoteAnimationTargetCompat[] apps,
+            RemoteAnimationTargetCompat[] wallpapers, Rect homeContentInsets,
+            Rect minimizedHomeBounds) {
+        super(apps, wallpapers, MODE_CLOSING);
+        this.homeContentInsets = homeContentInsets;
+        this.minimizedHomeBounds = minimizedHomeBounds;
+    }
+
+    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/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index e86a1c1..465d464 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -29,12 +29,9 @@
 import android.os.Build;
 import android.os.Looper;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
-import android.util.Log;
 
 import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -60,8 +57,6 @@
     private final List<TaskThumbnailChangeListener> mThumbnailChangeListeners = new ArrayList<>();
     private final Context mContext;
 
-    private ISystemUiProxy mSystemUiProxy;
-
     private final RecentTasksList mTaskList;
     private final TaskIconCache mIconCache;
     private final TaskThumbnailCache mThumbnailCache;
@@ -177,14 +172,6 @@
         mIconCache.onTaskRemoved(dummyKey);
     }
 
-    public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {
-        mSystemUiProxy = systemUiProxy;
-    }
-
-    public ISystemUiProxy getSystemUiProxy() {
-        return mSystemUiProxy;
-    }
-
     public void onTrimMemory(int level) {
         if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
             mThumbnailCache.getHighResLoadingState().setVisible(false);
@@ -197,16 +184,7 @@
     }
 
     public void onOverviewShown(boolean fromHome, String tag) {
-        if (mSystemUiProxy == null) {
-            return;
-        }
-        try {
-            mSystemUiProxy.onOverviewShown(fromHome);
-        } catch (RemoteException e) {
-            Log.w(tag,
-                    "Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app")
-                            + ": ", e);
-        }
+        SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(fromHome, tag);
     }
 
     private void setupPackageListener() {
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
similarity index 95%
rename from quickstep/src/com/android/quickstep/util/RemoteAnimationTargets.java
rename to quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
index ff726a1..5fa6bc7 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.quickstep.util;
+package com.android.quickstep;
 
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -86,6 +86,9 @@
             for (RemoteAnimationTargetCompat target : unfilteredApps) {
                 target.release();
             }
+            for (RemoteAnimationTargetCompat target : wallpapers) {
+                target.release();
+            }
         } else {
             applier.addAfterApplyCallback(this::release);
         }
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index b67c6f8..5902672 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -57,7 +57,7 @@
 
     private static final String TAG = "SysUINavigationMode";
 
-    private final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
+    private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
     private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
             "config_navBarInteractionMode";
 
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
new file mode 100644
index 0000000..5539b3e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -0,0 +1,285 @@
+/*
+ * 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.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.MotionEvent;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.quickstep.util.SharedApiCompat;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+
+/**
+ * Holds the reference to SystemUI.
+ */
+public class SystemUiProxy implements ISystemUiProxy {
+    private static final String TAG = SystemUiProxy.class.getSimpleName();
+
+    public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
+            new MainThreadInitializedObject<>(SystemUiProxy::new);
+
+    private ISystemUiProxy mSystemUiProxy;
+    private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
+        MAIN_EXECUTOR.execute(() -> setProxy(null));
+    };
+
+    // Used to dedupe calls to SystemUI
+    private int mLastShelfHeight;
+    private boolean mLastShelfVisible;
+    private float mLastBackButtonAlpha;
+    private boolean mLastBackButtonAnimate;
+
+    // TODO(141886704): Find a way to remove this
+    private int mLastSystemUiStateFlags;
+
+    public SystemUiProxy(Context context) {
+        // Do nothing
+    }
+
+    @Override
+    public IBinder asBinder() {
+        // Do nothing
+        return null;
+    }
+
+    public void setProxy(ISystemUiProxy proxy) {
+        unlinkToDeath();
+        mSystemUiProxy = proxy;
+        linkToDeath();
+    }
+
+    // TODO(141886704): Find a way to remove this
+    public void setLastSystemUiStateFlags(int stateFlags) {
+        mLastSystemUiStateFlags = stateFlags;
+    }
+
+    // TODO(141886704): Find a way to remove this
+    public int getLastSystemUiStateFlags() {
+        return mLastSystemUiStateFlags;
+    }
+
+    public boolean isActive() {
+        return mSystemUiProxy != null;
+    }
+
+    private void linkToDeath() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.asBinder().linkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to link sysui proxy death recipient");
+            }
+        }
+    }
+
+    private void unlinkToDeath() {
+        if (mSystemUiProxy != null) {
+            mSystemUiProxy.asBinder().unlinkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
+        }
+    }
+
+    @Override
+    public void startScreenPinning(int taskId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.startScreenPinning(taskId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startScreenPinning", e);
+            }
+        }
+    }
+
+    @Override
+    public void onSplitScreenInvoked() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onSplitScreenInvoked();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onSplitScreenInvoked", e);
+            }
+        }
+    }
+
+    @Override
+    public void onOverviewShown(boolean fromHome) {
+        onOverviewShown(fromHome, TAG);
+    }
+
+    public void onOverviewShown(boolean fromHome, String tag) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onOverviewShown(fromHome);
+            } catch (RemoteException e) {
+                Log.w(tag, "Failed call onOverviewShown from: " + (fromHome ? "home" : "app"), e);
+            }
+        }
+    }
+
+    @Override
+    public Rect getNonMinimizedSplitScreenSecondaryBounds() {
+        if (mSystemUiProxy != null) {
+            try {
+                return mSystemUiProxy.getNonMinimizedSplitScreenSecondaryBounds();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call getNonMinimizedSplitScreenSecondaryBounds", e);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void setBackButtonAlpha(float alpha, boolean animate) {
+        boolean changed = Float.compare(alpha, mLastBackButtonAlpha) != 0
+                || animate != mLastBackButtonAnimate;
+        if (mSystemUiProxy != null && changed) {
+            mLastBackButtonAlpha = alpha;
+            mLastBackButtonAnimate = animate;
+            try {
+                mSystemUiProxy.setBackButtonAlpha(alpha, animate);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setBackButtonAlpha", e);
+            }
+        }
+    }
+
+    public float getLastBackButtonAlpha() {
+        return mLastBackButtonAlpha;
+    }
+
+    @Override
+    public void setNavBarButtonAlpha(float alpha, boolean animate) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setNavBarButtonAlpha", e);
+            }
+        }
+    }
+
+    @Override
+    public void onStatusBarMotionEvent(MotionEvent event) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onStatusBarMotionEvent(event);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onStatusBarMotionEvent", e);
+            }
+        }
+    }
+
+    @Override
+    public void onAssistantProgress(float progress) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onAssistantProgress(progress);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onAssistantProgress with progress: " + progress, e);
+            }
+        }
+    }
+
+    @Override
+    public void onAssistantGestureCompletion(float velocity) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.onAssistantGestureCompletion(velocity);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call onAssistantGestureCompletion", e);
+            }
+        }
+    }
+
+    @Override
+    public void startAssistant(Bundle args) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.startAssistant(args);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startAssistant", e);
+            }
+        }
+    }
+
+    @Override
+    public Bundle monitorGestureInput(String name, int displayId) {
+        if (mSystemUiProxy != null) {
+            try {
+                return mSystemUiProxy.monitorGestureInput(name, displayId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call monitorGestureInput: " + name, e);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void notifyAccessibilityButtonClicked(int displayId) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.notifyAccessibilityButtonClicked(displayId);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call notifyAccessibilityButtonClicked", e);
+            }
+        }
+    }
+
+    @Override
+    public void notifyAccessibilityButtonLongClicked() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.notifyAccessibilityButtonLongClicked();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call notifyAccessibilityButtonLongClicked", e);
+            }
+        }
+    }
+
+    @Override
+    public void stopScreenPinning() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.stopScreenPinning();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call stopScreenPinning", e);
+            }
+        }
+    }
+
+    /**
+     * See SharedApiCompat#setShelfHeight()
+     */
+    public void setShelfHeight(boolean visible, int shelfHeight) {
+        boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
+        if (mSystemUiProxy != null && changed) {
+            mLastShelfVisible = visible;
+            mLastShelfHeight = shelfHeight;
+            try {
+                SharedApiCompat.setShelfHeight(mSystemUiProxy, visible, shelfHeight);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call setShelfHeight visible: " + visible
+                        + " height: " + shelfHeight, e);
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
index 7f6aba9..fa2d338 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteFadeOutAnimationListener.java
@@ -21,6 +21,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 
+import com.android.quickstep.RemoteAnimationTargets;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
 
diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java
index f8d1632..a133f01 100644
--- a/src/com/android/launcher3/util/UiThreadHelper.java
+++ b/src/com/android/launcher3/util/UiThreadHelper.java
@@ -23,6 +23,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.view.inputmethod.InputMethodManager;
+import com.android.launcher3.uioverrides.UiFactory;
 
 /**
  * Utility class for offloading some class from UI thread
@@ -52,15 +53,22 @@
                 .sendToTarget();
     }
 
+    public static void setBackButtonAlphaAsync(Context context, AsyncCommand command, float alpha,
+            boolean animate) {
+        runAsyncCommand(context, command, Float.floatToIntBits(alpha), animate ? 1 : 0);
+    }
+
     public static void runAsyncCommand(Context context, AsyncCommand command, int arg1, int arg2) {
         Message.obtain(getHandler(context), MSG_RUN_COMMAND, arg1, arg2, command).sendToTarget();
     }
 
     private static class UiCallbacks implements Handler.Callback {
 
+        private final Context mContext;
         private final InputMethodManager mIMM;
 
         UiCallbacks(Context context) {
+            mContext = context;
             mIMM = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
         }
 
@@ -74,7 +82,7 @@
                     ((Activity) message.obj).setRequestedOrientation(message.arg1);
                     return true;
                 case MSG_RUN_COMMAND:
-                    ((AsyncCommand) message.obj).execute(message.arg1, message.arg2);
+                    ((AsyncCommand) message.obj).execute(mContext, message.arg1, message.arg2);
                     return true;
             }
             return false;
@@ -82,7 +90,6 @@
     }
 
     public interface AsyncCommand {
-
-        void execute(int arg1, int arg2);
+        void execute(Context proxy, int arg1, int arg2);
     }
 }