Unifying FallbackNoButton input consumer with OtherActivityInputConsumer
using a different handler

This will allow us to use common logic for handling horizontal swipe

Bug: 137197916
Change-Id: I6f9cba6e8728dd0669482906c4bf34270af2bc82
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 6e4a408..d34b604 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -22,22 +22,32 @@
 import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
 
 import android.annotation.TargetApi;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.graphics.PointF;
 import android.os.Build;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.provider.Settings;
+import android.view.MotionEvent;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
+import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
+import com.android.quickstep.views.RecentsView;
+
+import java.util.function.Consumer;
+
+import androidx.annotation.UiThread;
 
 /**
  * Base class for swipe up handler with some utility methods
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public abstract class BaseSwipeUpHandler {
+public abstract class BaseSwipeUpHandler implements SwipeAnimationListener {
 
     // Start resisting when swiping past this factor of mTransitionDragLength.
     private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
@@ -57,6 +67,16 @@
 
     private final Vibrator mVibrator;
 
+    // Shift in the range of [0, 1].
+    // 0 => preview snapShot is completely visible, and hotseat is completely translated down
+    // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
+    // visible.
+    protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+    protected RecentsView mRecentsView;
+
+    protected Runnable mGestureEndCallback;
+
     protected BaseSwipeUpHandler(Context context) {
         mContext = context;
         mClipAnimationHelper = new ClipAnimationHelper(context);
@@ -80,14 +100,20 @@
         BACKGROUND_EXECUTOR.execute(() -> mVibrator.vibrate(effect));
     }
 
-    protected float getShiftForDisplacement(float displacement) {
+    public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
+        return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
+    }
+
+    @UiThread
+    public void updateDisplacement(float displacement) {
         // We are moving in the negative x/y direction
         displacement = -displacement;
+        float shift;
         if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
-            return mDragLengthFactor;
+            shift = mDragLengthFactor;
         } else {
             float translation = Math.max(displacement, 0);
-            float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+            shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
             if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
                 float pullbackProgress = Utilities.getProgress(shift,
                         DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
@@ -95,7 +121,44 @@
                 shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
                         * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
             }
-            return shift;
         }
+
+        mCurrentShift.updateValue(shift);
+    }
+
+    public void setGestureEndCallback(Runnable gestureEndCallback) {
+        mGestureEndCallback = gestureEndCallback;
+    }
+
+    /**
+     * Called when the value of {@link #mCurrentShift} changes
+     */
+    public abstract void updateFinalShift();
+
+
+    /**
+     * Called when motion pause is detected
+     */
+    public abstract void onMotionPauseChanged(boolean isPaused);
+
+    @UiThread
+    public void onGestureStarted() { }
+
+    @UiThread
+    public abstract void onGestureCancelled();
+
+    @UiThread
+    public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
+
+    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) { }
+
+    public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
+
+    public void initWhenReady() { }
+
+    public interface Factory {
+
+        BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
+                long touchTimeMs, boolean continuingLastGesture);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 52e8ba2..61767e5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -199,6 +199,12 @@
         mFallbackRecentsView.resetTaskVisuals();
     }
 
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mFallbackRecentsView.reset();
+    }
+
     public void onTaskLaunched() {
         mFallbackRecentsView.resetTaskVisuals();
     }
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 0ef2f5c..fafa4d3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -239,6 +239,11 @@
     private final InputConsumer mResetGestureInputConsumer =
             new ResetGestureInputConsumer(sSwipeSharedState);
 
+    private final BaseSwipeUpHandler.Factory mWindowTreansformFactory =
+            this::createWindowTransformSwipeHandler;
+    private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory =
+            this::createFallbackNoButtonSwipeHandler;
+
     private ActivityManagerWrapper mAM;
     private RecentsModel mRecentsModel;
     private ISystemUiProxy mISystemUiProxy;
@@ -623,10 +628,6 @@
         } else if (mGestureBlockingActivity != null && runningTaskInfo != null
                 && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
             return mResetGestureInputConsumer;
-        } else if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
-            return new FallbackNoButtonInputConsumer(this, activityControl,
-                    mInputMonitorCompat, sSwipeSharedState, mSwipeTouchRegion,
-                    mOverviewComponentObserver, disableHorizontalSwipe(event), runningTaskInfo);
         } else {
             return createOtherActivityInputConsumer(event, runningTaskInfo);
         }
@@ -639,17 +640,28 @@
                 && exclusionRegion.contains((int) event.getX(), (int) event.getY());
     }
 
-    private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
+    private InputConsumer createOtherActivityInputConsumer(MotionEvent event,
             RunningTaskInfo runningTaskInfo) {
-        final ActivityControlHelper activityControl =
-                mOverviewComponentObserver.getActivityControlHelper();
-        boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
 
-        return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
-                mOverviewComponentObserver.getOverviewIntent(), activityControl,
-                shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive,
+        final boolean shouldDefer;
+        final BaseSwipeUpHandler.Factory factory;
+        final Intent homeIntent;
+
+        if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+            shouldDefer = true;
+            factory = mFallbackNoButtonFactory;
+            homeIntent = mOverviewComponentObserver.getHomeIntent();
+        } else {
+            shouldDefer = mOverviewComponentObserver.getActivityControlHelper()
+                    .deferStartingActivity(mActiveNavBarRegion, event);
+            factory = mWindowTreansformFactory;
+            homeIntent = mOverviewComponentObserver.getOverviewIntent();
+        }
+
+        return new OtherActivityInputConsumer(this, runningTaskInfo, homeIntent,
+                shouldDefer, mOverviewCallbacks, this::onConsumerInactive,
                 sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
-                disableHorizontalSwipe(event));
+                disableHorizontalSwipe(event), factory);
     }
 
     private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
@@ -788,6 +800,19 @@
         }
     }
 
+    private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
+            long touchTimeMs, boolean continuingLastGesture) {
+        return  new WindowTransformSwipeHandler(
+                runningTask, this, touchTimeMs,
+                mOverviewComponentObserver.getActivityControlHelper(),
+                continuingLastGesture, mInputConsumer, mRecentsModel);
+    }
+
+    private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(RunningTaskInfo runningTask,
+            long touchTimeMs, boolean continuingLastGesture) {
+        return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask);
+    }
+
     public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
         BACKGROUND_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
                 .startRecentsActivity(intent, null, listener, null, null));
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 da9c6cb..9684eec 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -58,7 +58,6 @@
 import android.os.Looper;
 import android.os.SystemClock;
 import android.util.Log;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
@@ -66,10 +65,6 @@
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
@@ -79,7 +74,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
@@ -99,9 +93,7 @@
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
 import com.android.quickstep.views.LiveTileOverlay;
-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;
@@ -110,11 +102,12 @@
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.WindowCallbacksCompat;
 
-import java.util.function.Consumer;
+import androidx.annotation.NonNull;
+import androidx.annotation.UiThread;
 
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends BaseSwipeUpHandler
-        implements SwipeAnimationListener, OnApplyWindowInsetsListener {
+        implements OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
     private static final Rect TEMP_RECT = new Rect();
@@ -222,18 +215,12 @@
      */
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
-    private Runnable mGestureEndCallback;
     private GestureEndTarget mGestureEndTarget;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
     private boolean mIsShelfPeeking;
     private DeviceProfile mDp;
 
-    // Shift in the range of [0, 1].
-    // 0 => preview snapShot is completely visible, and hotseat is completely translated down
-    // 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
-    // visible.
-    private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
     private boolean mContinuingLastGesture;
     // To avoid UI jump when gesture is started, we offset the animation by the threshold.
     private float mShiftAtGestureStart = 0;
@@ -242,6 +229,7 @@
 
     private final ActivityControlHelper<T> mActivityControlHelper;
     private final ActivityInitListener mActivityInitListener;
+    private final RecentsModel mRecentsModel;
 
     private final SysUINavigationMode.Mode mMode;
 
@@ -254,7 +242,6 @@
     private boolean mHasLauncherTransitionControllerStarted;
 
     private T mActivity;
-    private RecentsView mRecentsView;
     private AnimationFactory mAnimationFactory = (t) -> { };
     private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
 
@@ -276,11 +263,12 @@
 
     public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
             long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
-            InputConsumerController inputConsumer) {
+            InputConsumerController inputConsumer, RecentsModel recentsModel) {
         super(context);
         mRunningTaskId = runningTaskInfo.id;
         mTouchTimeMs = touchTimeMs;
         mActivityControlHelper = controller;
+        mRecentsModel = recentsModel;
         mActivityInitListener = mActivityControlHelper
                 .createActivityInitListener(this::onActivityInit);
         mContinuingLastGesture = continuingLastGesture;
@@ -375,7 +363,11 @@
         }
     }
 
+    @Override
     public void initWhenReady() {
+        // Preload the plan
+        mRecentsModel.getTasks(null);
+
         mActivityInitListener.register();
     }
 
@@ -546,15 +538,7 @@
         return TaskView.getCurveScaleForInterpolation(interpolation);
     }
 
-    public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
-        return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
-    }
-
-    @UiThread
-    public void updateDisplacement(float displacement) {
-        mCurrentShift.updateValue(getShiftForDisplacement(displacement));
-    }
-
+    @Override
     public void onMotionPauseChanged(boolean isPaused) {
         setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
     }
@@ -605,6 +589,7 @@
         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
     }
 
+    @Override
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
@@ -651,7 +636,8 @@
     }
 
     @UiThread
-    private void updateFinalShift() {
+    @Override
+    public void updateFinalShift() {
         float shift = mCurrentShift.value;
 
         SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
@@ -766,7 +752,7 @@
         TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
     }
 
-    @UiThread
+    @Override
     public void onGestureStarted() {
         notifyGestureStartedAsync();
         mShiftAtGestureStart = mCurrentShift.value;
@@ -790,7 +776,7 @@
     /**
      * Called as a result on ACTION_CANCEL to return the UI to the start state.
      */
-    @UiThread
+    @Override
     public void onGestureCancelled() {
         updateDisplacement(0);
         setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -803,7 +789,7 @@
      * @param velocity The x and y components of the velocity when the gesture ends.
      * @param downPos The x and y value of where the gesture started.
      */
-    @UiThread
+    @Override
     public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_velocity);
@@ -1174,11 +1160,18 @@
         return anim;
     }
 
-    /**
-     * @return The GestureEndTarget if the gesture has ended, else null.
-     */
-    public @Nullable GestureEndTarget getGestureEndTarget() {
-        return mGestureEndTarget;
+    @Override
+    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
+        if (mGestureEndTarget != null) {
+            sharedState.canGestureBeContinued = mGestureEndTarget.canBeContinued;
+            sharedState.goingToLauncher = mGestureEndTarget.isLauncher;
+        }
+
+        if (sharedState.canGestureBeContinued) {
+            cancelCurrentAnimation(sharedState);
+        } else {
+            reset();
+        }
     }
 
     @UiThread
@@ -1227,7 +1220,7 @@
         TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
     }
 
-    public void reset() {
+    private void reset() {
         setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
@@ -1235,7 +1228,7 @@
      * Cancels any running animation so that the active target can be overriden by a new swipe
      * handle (in case of quick switch).
      */
-    public void cancelCurrentAnimation(SwipeSharedState sharedState) {
+    private void cancelCurrentAnimation(SwipeSharedState sharedState) {
         mCanceled = true;
         mCurrentShift.cancelAnimation();
         if (mLauncherTransitionController != null && mLauncherTransitionController
@@ -1398,10 +1391,6 @@
         reset();
     }
 
-    public void setGestureEndCallback(Runnable gestureEndCallback) {
-        mGestureEndCallback = gestureEndCallback;
-    }
-
     private void setTargetAlphaProvider(TargetAlphaProvider provider) {
         mClipAnimationHelper.setTaskAlphaCallback(provider);
         updateFinalShift();
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 cdd91b7..a5a8f38 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
@@ -15,23 +15,12 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_POINTER_DOWN;
-import static android.view.MotionEvent.ACTION_POINTER_UP;
-import static android.view.MotionEvent.ACTION_UP;
-import static android.view.MotionEvent.INVALID_POINTER_ID;
-
 import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
 import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
 import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.HOME;
 import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.RECENTS;
-import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
-import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -42,11 +31,7 @@
 import android.content.Intent;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Bundle;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
 import com.android.launcher3.DeviceProfile;
@@ -56,20 +41,12 @@
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseSwipeUpHandler;
 import com.android.quickstep.OverviewComponentObserver;
-import com.android.quickstep.SwipeSharedState;
-import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.ObjectWrapper;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler
-        implements InputConsumer, SwipeAnimationListener {
+public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler {
 
     public enum GestureEndTarget {
         HOME(3, 100, 1),
@@ -79,6 +56,7 @@
         private final float mEndProgress;
         private final long mDurationMultiplier;
         private final float mLauncherAlpha;
+
         GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) {
             mEndProgress = endProgress;
             mDurationMultiplier = durationMultiplier;
@@ -87,68 +65,26 @@
     }
 
     private final ActivityControlHelper mActivityControlHelper;
-    private final InputMonitorCompat mInputMonitor;
-    private final NavBarPosition mNavBarPosition;
-    private final SwipeSharedState mSwipeSharedState;
     private final OverviewComponentObserver mOverviewComponentObserver;
     private final int mRunningTaskId;
 
     private final DeviceProfile mDP;
-    private final MotionPauseDetector mMotionPauseDetector;
-    private final float mMotionPauseMinDisplacement;
     private final Rect mTargetRect = new Rect();
 
-    private final RectF mSwipeTouchRegion;
-    private final boolean mDisableHorizontalSwipe;
-
-    private final PointF mDownPos = new PointF();
-    private final PointF mLastPos = new PointF();
-
     private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
-    private final AnimatedFloat mProgress = new AnimatedFloat(this::onProgressChanged);
 
-    private int mActivePointerId = -1;
-    // Slop used to determine when we say that the gesture has started.
-    private boolean mPassedPilferInputSlop;
-
-    private VelocityTracker mVelocityTracker;
-
-    // Distance after which we start dragging the window.
-    private final float mTouchSlop;
-
-    // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
-    private float mStartDisplacement;
     private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
 
     private boolean mIsMotionPaused = false;
     private GestureEndTarget mEndTarget;
 
     public FallbackNoButtonInputConsumer(Context context,
-            ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
-            SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
             OverviewComponentObserver overviewComponentObserver,
-            boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
+            RunningTaskInfo runningTaskInfo) {
         super(context);
-        mActivityControlHelper = activityControlHelper;
-        mInputMonitor = inputMonitor;
         mOverviewComponentObserver = overviewComponentObserver;
+        mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
         mRunningTaskId = runningTaskInfo.id;
-
-        mMotionPauseDetector = new MotionPauseDetector(context);
-        mMotionPauseMinDisplacement = context.getResources().getDimension(
-                R.dimen.motion_pause_detector_min_displacement_from_app);
-        mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
-
-        mSwipeSharedState = swipeSharedState;
-        mSwipeTouchRegion = swipeTouchRegion;
-        mDisableHorizontalSwipe = disableHorizontalSwipe;
-
-        mNavBarPosition = new NavBarPosition(context);
-        mVelocityTracker = VelocityTracker.obtain();
-
-        mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
-                * ViewConfiguration.get(context).getScaledTouchSlop();
-
         mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
         mLauncherAlpha.value = 1;
 
@@ -156,18 +92,14 @@
         initTransitionTarget();
     }
 
-    @Override
-    public int getType() {
-        return TYPE_FALLBACK_NO_BUTTON;
-    }
-
     private void onLauncherAlphaChanged() {
         if (mSwipeAnimationTargetSet != null && mEndTarget == null) {
             mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
         }
     }
 
-    private void onMotionPauseChanged(boolean isPaused) {
+    @Override
+    public void onMotionPauseChanged(boolean isPaused) {
         mIsMotionPaused = isPaused;
         mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
                 .setDuration(150).start();
@@ -175,150 +107,36 @@
     }
 
     @Override
-    public void onMotionEvent(MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            return;
-        }
-
-        mVelocityTracker.addMovement(ev);
-        if (ev.getActionMasked() == ACTION_POINTER_UP) {
-            mVelocityTracker.clear();
-            mMotionPauseDetector.clear();
-        }
-
-        switch (ev.getActionMasked()) {
-            case ACTION_DOWN: {
-                mActivePointerId = ev.getPointerId(0);
-                mDownPos.set(ev.getX(), ev.getY());
-                mLastPos.set(mDownPos);
-                break;
-            }
-            case ACTION_POINTER_DOWN: {
-                if (!mPassedPilferInputSlop) {
-                    // Cancel interaction in case of multi-touch interaction
-                    int ptrIdx = ev.getActionIndex();
-                    if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) {
-                        forceCancelGesture(ev);
-                    }
-                }
-                break;
-            }
-            case ACTION_POINTER_UP: {
-                int ptrIdx = ev.getActionIndex();
-                int ptrId = ev.getPointerId(ptrIdx);
-                if (ptrId == mActivePointerId) {
-                    final int newPointerIdx = ptrIdx == 0 ? 1 : 0;
-                    mDownPos.set(
-                            ev.getX(newPointerIdx) - (mLastPos.x - mDownPos.x),
-                            ev.getY(newPointerIdx) - (mLastPos.y - mDownPos.y));
-                    mLastPos.set(ev.getX(newPointerIdx), ev.getY(newPointerIdx));
-                    mActivePointerId = ev.getPointerId(newPointerIdx);
-                }
-                break;
-            }
-            case ACTION_MOVE: {
-                int pointerIndex = ev.findPointerIndex(mActivePointerId);
-                if (pointerIndex == INVALID_POINTER_ID) {
-                    break;
-                }
-                mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
-                float displacement = getDisplacement(ev);
-
-                if (!mPassedPilferInputSlop) {
-                    if (mDisableHorizontalSwipe && Math.abs(mLastPos.x - mDownPos.x)
-                            > Math.abs(mLastPos.y - mDownPos.y)) {
-                        // Horizontal gesture is not allowed in this region
-                        forceCancelGesture(ev);
-                        break;
-                    }
-
-                    if (Math.abs(displacement) >= mTouchSlop) {
-                        mPassedPilferInputSlop = true;
-
-                        // Deferred gesture, start the animation and gesture tracking once
-                        // we pass the actual touch slop
-                        startTouchTrackingForWindowAnimation(displacement);
-                    }
-                } else {
-                    updateDisplacement(displacement - mStartDisplacement);
-                    mMotionPauseDetector.setDisallowPause(
-                            -displacement < mMotionPauseMinDisplacement);
-                    mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
-                }
-                break;
-            }
-            case ACTION_CANCEL:
-            case ACTION_UP: {
-                finishTouchTracking(ev);
-                break;
-            }
-        }
-    }
-
-    private void startTouchTrackingForWindowAnimation(float displacement) {
-        mStartDisplacement = Math.min(displacement, -mTouchSlop);
-
-        RecentsAnimationListenerSet listenerSet =
-                mSwipeSharedState.newRecentsAnimationListenerSet();
-        listenerSet.addListener(this);
-        Intent homeIntent = mOverviewComponentObserver.getHomeIntent();
-        startRecentsActivityAsync(homeIntent, listenerSet);
-
-        ActivityManagerWrapper.getInstance().closeSystemWindows(
-                CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        mInputMonitor.pilferPointers();
-    }
-
-    private void updateDisplacement(float displacement) {
-        mProgress.updateValue(getShiftForDisplacement(displacement));
-    }
-
-    private void onProgressChanged() {
-        mTransformParams.setProgress(mProgress.value);
+    public void updateFinalShift() {
+        mTransformParams.setProgress(mCurrentShift.value);
         if (mSwipeAnimationTargetSet != null) {
             mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
         }
     }
 
-    private void forceCancelGesture(MotionEvent ev) {
-        int action = ev.getAction();
-        ev.setAction(ACTION_CANCEL);
-        finishTouchTracking(ev);
-        ev.setAction(action);
+    @Override
+    public void onGestureCancelled() {
+        updateDisplacement(0);
+        mEndTarget = LAST_TASK;
+        finishAnimationTargetSetAnimationComplete();
     }
 
-    /**
-     * Called when the gesture has ended. Does not correlate to the completion of the interaction as
-     * the animation can still be running.
-     */
-    private void finishTouchTracking(MotionEvent ev) {
-        if (ev.getAction() == ACTION_CANCEL) {
-            mEndTarget = LAST_TASK;
+    @Override
+    public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
+        float flingThreshold = mContext.getResources()
+                .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+        boolean isFling = Math.abs(endVelocity) > flingThreshold;
+
+        if (isFling) {
+            mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
+        } else if (mIsMotionPaused) {
+            mEndTarget = RECENTS;
         } else {
-            mVelocityTracker.computeCurrentVelocity(1000,
-                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
-            float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
-            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
-            float velocity = mNavBarPosition.isRightEdge() ? velocityX
-                    : mNavBarPosition.isLeftEdge() ? -velocityX
-                            : velocityY;
-            float flingThreshold = mContext.getResources()
-                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
-            boolean isFling = Math.abs(velocity) > flingThreshold;
-
-            if (isFling) {
-                mEndTarget = velocity < 0 ? HOME : LAST_TASK;
-            } else if (mIsMotionPaused) {
-                mEndTarget = RECENTS;
-            } else {
-                mEndTarget = mProgress.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
-            }
+            mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
         }
-
         if (mSwipeAnimationTargetSet != null) {
             finishAnimationTargetSet();
         }
-        mMotionPauseDetector.clear();
     }
 
     private void finishAnimationTargetSetAnimationComplete() {
@@ -346,19 +164,22 @@
                 break;
             }
         }
+        if (mGestureEndCallback != null) {
+            mGestureEndCallback.run();
+        }
     }
 
     private void finishAnimationTargetSet() {
         float endProgress = mEndTarget.mEndProgress;
 
-        if (mProgress.value != endProgress) {
+        if (mCurrentShift.value != endProgress) {
             AnimatorSet anim = new AnimatorSet();
             anim.play(mLauncherAlpha.animateToValue(
                     mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
-            anim.play(mProgress.animateToValue(mProgress.value, endProgress));
+            anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
 
             anim.setDuration((long) (mEndTarget.mDurationMultiplier *
-                    Math.abs(endProgress - mProgress.value)));
+                    Math.abs(endProgress - mCurrentShift.value)));
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -403,19 +224,4 @@
 
     @Override
     public void onRecentsAnimationCanceled() { }
-
-    private float getDisplacement(MotionEvent ev) {
-        if (mNavBarPosition.isRightEdge()) {
-            return ev.getX() - mDownPos.x;
-        } else if (mNavBarPosition.isLeftEdge()) {
-            return mDownPos.x - ev.getX();
-        } else {
-            return ev.getY() - mDownPos.y;
-        }
-    }
-
-    @Override
-    public boolean allowInterceptByParent() {
-        return !mPassedPilferInputSlop;
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index f5cf654..a1e5d47 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -33,7 +33,6 @@
     int TYPE_SCREEN_PINNED = 1 << 6;
     int TYPE_OVERVIEW_WITHOUT_FOCUS = 1 << 7;
     int TYPE_RESET_GESTURE = 1 << 8;
-    int TYPE_FALLBACK_NO_BUTTON = 1 << 9;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -45,7 +44,6 @@
             "TYPE_SCREEN_PINNED",           // 6
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
-            "TYPE_FALLBACK_NO_BUTTON",      // 9
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
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 a4d2f39..9114995 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
@@ -49,20 +49,16 @@
 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.BaseSwipeUpHandler;
 import com.android.quickstep.OverviewCallbacks;
-import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SwipeSharedState;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
-import com.android.quickstep.WindowTransformSwipeHandler;
-import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 import java.util.function.Consumer;
@@ -83,16 +79,15 @@
 
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
-    private final RecentsModel mRecentsModel;
     private final Intent mHomeIntent;
-    private final ActivityControlHelper mActivityControlHelper;
     private final OverviewCallbacks mOverviewCallbacks;
-    private final InputConsumerController mInputConsumer;
     private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
     private final SysUINavigationMode.Mode mMode;
     private final RectF mSwipeTouchRegion;
 
+    private final BaseSwipeUpHandler.Factory mHandlerFactory;
+
     private final NavBarPosition mNavBarPosition;
 
     private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
@@ -100,7 +95,7 @@
     private final float mMotionPauseMinDisplacement;
     private VelocityTracker mVelocityTracker;
 
-    private WindowTransformSwipeHandler mInteractionHandler;
+    private BaseSwipeUpHandler mInteractionHandler;
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -114,7 +109,7 @@
     private final boolean mDisableHorizontalSwipe;
 
     // Slop used to check when we start moving window.
-    private boolean mPaddedWindowMoveSlop;
+    private boolean mPassedWindowMoveSlop;
     // Slop used to determine when we say that the gesture has started.
     private boolean mPassedPilferInputSlop;
 
@@ -122,26 +117,24 @@
     private float mStartDisplacement;
 
     private Handler mMainThreadHandler;
-    private Runnable mCancelRecentsAnimationRunnable = () -> {
+    private Runnable mCancelRecentsAnimationRunnable = () ->
         ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
                 true /* restoreHomeStackPosition */);
-    };
 
     public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
-            RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
-            boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
-            InputConsumerController inputConsumer,
+            Intent homeIntent, boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
             Consumer<OtherActivityInputConsumer> onCompleteCallback,
             SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
-            RectF swipeTouchRegion, boolean disableHorizontalSwipe) {
+            RectF swipeTouchRegion, boolean disableHorizontalSwipe,
+            BaseSwipeUpHandler.Factory handlerFactory) {
         super(base);
 
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         mRunningTask = runningTaskInfo;
-        mRecentsModel = recentsModel;
         mHomeIntent = homeIntent;
         mMode = SysUINavigationMode.getMode(base);
         mSwipeTouchRegion = swipeTouchRegion;
+        mHandlerFactory = handlerFactory;
 
         mMotionPauseDetector = new MotionPauseDetector(base);
         mMotionPauseMinDisplacement = base.getResources().getDimension(
@@ -150,11 +143,9 @@
         mVelocityTracker = VelocityTracker.obtain();
         mInputMonitorCompat = inputMonitorCompat;
 
-        mActivityControlHelper = activityControl;
         boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
         mOverviewCallbacks = overviewCallbacks;
-        mInputConsumer = inputConsumer;
         mSwipeSharedState = swipeSharedState;
 
         mNavBarPosition = new NavBarPosition(base);
@@ -163,7 +154,7 @@
         float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
         mSquaredTouchSlop = slop * slop;
 
-        mPassedPilferInputSlop = mPaddedWindowMoveSlop = continuingPreviousGesture;
+        mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
     }
 
@@ -186,7 +177,7 @@
         }
 
         // Proxy events to recents view
-        if (mPaddedWindowMoveSlop && mInteractionHandler != null
+        if (mPassedWindowMoveSlop && mInteractionHandler != null
                 && !mRecentsViewDispatcher.hasConsumer()) {
             mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
                     mNavBarPosition.getRotationMode()));
@@ -251,12 +242,12 @@
                 float displacement = getDisplacement(ev);
                 float displacementX = mLastPos.x - mDownPos.x;
 
-                if (!mPaddedWindowMoveSlop) {
+                if (!mPassedWindowMoveSlop) {
                     if (!mIsDeferredDownTarget) {
                         // Normal gesture, ensure we pass the drag slop before we start tracking
                         // the gesture
                         if (Math.abs(displacement) > mTouchSlop) {
-                            mPaddedWindowMoveSlop = true;
+                            mPassedWindowMoveSlop = true;
                             mStartDisplacement = Math.min(displacement, -mTouchSlop);
                         }
                     }
@@ -279,8 +270,8 @@
                             // we pass the actual touch slop
                             startTouchTrackingForWindowAnimation(ev.getEventTime());
                         }
-                        if (!mPaddedWindowMoveSlop) {
-                            mPaddedWindowMoveSlop = true;
+                        if (!mPassedWindowMoveSlop) {
+                            mPassedWindowMoveSlop = true;
                             mStartDisplacement = Math.min(displacement, -mTouchSlop);
 
                         }
@@ -289,7 +280,7 @@
                 }
 
                 if (mInteractionHandler != null) {
-                    if (mPaddedWindowMoveSlop) {
+                    if (mPassedWindowMoveSlop) {
                         // Move
                         mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
                     }
@@ -333,12 +324,9 @@
         TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
 
         RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
-        final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
-                mRunningTask, this, touchTimeMs, mActivityControlHelper,
-                listenerSet != null, mInputConsumer);
+        final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mRunningTask, touchTimeMs,
+                listenerSet != null);
 
-        // Preload the plan
-        mRecentsModel.getTasks(null);
         mInteractionHandler = handler;
         handler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
@@ -364,7 +352,7 @@
         RaceConditionTracker.onEvent(UP_EVT, ENTER);
         TraceHelper.endSection("TouchInt");
 
-        if (mPaddedWindowMoveSlop && mInteractionHandler != null) {
+        if (mPassedWindowMoveSlop && mInteractionHandler != null) {
             if (ev.getActionMasked() == ACTION_CANCEL) {
                 mInteractionHandler.onGestureCancelled();
             } else {
@@ -407,14 +395,7 @@
             // The consumer is being switched while we are active. Set up the shared state to be
             // used by the next animation
             removeListener();
-            GestureEndTarget endTarget = mInteractionHandler.getGestureEndTarget();
-            mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
-            mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
-            if (mSwipeSharedState.canGestureBeContinued) {
-                mInteractionHandler.cancelCurrentAnimation(mSwipeSharedState);
-            } else {
-                mInteractionHandler.reset();
-            }
+            mInteractionHandler.onConsumerAboutToBeSwitched(mSwipeSharedState);
         }
     }