Adding horizontal swipe support for 3p launcher

Bug: 137197916
Change-Id: I0fb5db791b3471d651db43f0e8c30b8d5baf9f27
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 d34b604..3032ca3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -18,26 +18,41 @@
 import static android.os.VibrationEffect.EFFECT_CLICK;
 import static android.os.VibrationEffect.createPredefined;
 
+import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
+import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
+import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.PointF;
 import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
 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.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.RotationMode;
+import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.inputconsumers.InputConsumer;
 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 com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 
 import java.util.function.Consumer;
 
@@ -47,7 +62,10 @@
  * Base class for swipe up handler with some utility methods
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public abstract class BaseSwipeUpHandler implements SwipeAnimationListener {
+public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity>
+        implements SwipeAnimationListener {
+
+    private static final String TAG = "BaseSwipeUpHandler";
 
     // Start resisting when swiping past this factor of mTransitionDragLength.
     private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
@@ -61,6 +79,9 @@
     protected float mDragLengthFactor = 1;
 
     protected final Context mContext;
+    protected final OverviewComponentObserver mOverviewComponentObserver;
+    protected final ActivityControlHelper<T> mActivityControlHelper;
+    protected final RecentsModel mRecentsModel;
 
     protected final ClipAnimationHelper mClipAnimationHelper;
     protected final TransformParams mTransformParams = new TransformParams();
@@ -73,18 +94,48 @@
     // visible.
     protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
 
+    protected final ActivityInitListener mActivityInitListener;
+    protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
+
+    protected T mActivity;
     protected RecentsView mRecentsView;
+    protected DeviceProfile mDp;
+    private final int mPageSpacing;
 
     protected Runnable mGestureEndCallback;
 
-    protected BaseSwipeUpHandler(Context context) {
-        mContext = context;
-        mClipAnimationHelper = new ClipAnimationHelper(context);
+    protected final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
+    protected MultiStateCallback mStateCallback;
 
+    protected boolean mCanceled;
+    protected int mFinishingRecentsAnimationForNewTaskId = -1;
+
+    protected BaseSwipeUpHandler(Context context,
+            OverviewComponentObserver overviewComponentObserver,
+            RecentsModel recentsModel, InputConsumerController inputConsumer) {
+        mContext = context;
+        mOverviewComponentObserver = overviewComponentObserver;
+        mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
+        mRecentsModel = recentsModel;
+        mActivityInitListener =
+                mActivityControlHelper.createActivityInitListener(this::onActivityInit);
+        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
+                this::createNewInputProxyHandler);
+
+        mClipAnimationHelper = new ClipAnimationHelper(context);
+        mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
         mVibrator = context.getSystemService(Vibrator.class);
     }
 
-    public void performHapticFeedback() {
+    protected void setStateOnUiThread(int stateFlag) {
+        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+            mStateCallback.setState(stateFlag);
+        } else {
+            postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
+        }
+    }
+
+    protected void performHapticFeedback() {
         if (!mVibrator.hasVibrator()) {
             return;
         }
@@ -130,12 +181,76 @@
         mGestureEndCallback = gestureEndCallback;
     }
 
+    public abstract Intent getLaunchIntent();
+
+    protected void linkRecentsViewScroll() {
+        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
+            mTransformParams.setSyncTransactionApplier(applier);
+            mRecentsAnimationWrapper.runOnInit(() ->
+                    mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
+        });
+
+        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            if (moveWindowWithRecentsScroll()) {
+                updateFinalShift();
+            }
+        });
+        mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
+        mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+    }
+
+    protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
+        // Launch the task user scrolled to (mRecentsView.getNextPage()).
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            // We finish recents animation inside launchTask() when live tile is enabled.
+            mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
+                    true /* freezeTaskList */);
+        } else {
+            int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
+            mFinishingRecentsAnimationForNewTaskId = taskId;
+            mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
+                if (!mCanceled) {
+                    TaskView nextTask = mRecentsView.getTaskView(taskId);
+                    if (nextTask != null) {
+                        nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+                                success -> {
+                                    resultCallback.accept(success);
+                                    if (!success) {
+                                        mActivityControlHelper.onLaunchTaskFailed(mActivity);
+                                        nextTask.notifyTaskLaunchFailed(TAG);
+                                    } else {
+                                        mActivityControlHelper.onLaunchTaskSuccess(mActivity);
+                                    }
+                                }, mMainThreadHandler);
+                    }
+                    setStateOnUiThread(successStateFlag);
+                }
+                mCanceled = false;
+                mFinishingRecentsAnimationForNewTaskId = -1;
+            });
+        }
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+    }
+
+    /**
+     * Return true if the window should be translated horizontally if the recents view scrolls
+     */
+    protected abstract boolean moveWindowWithRecentsScroll();
+
+    protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome);
+
+    /**
+     * Called to create a input proxy for the running task
+     */
+    @UiThread
+    protected abstract InputConsumer createNewInputProxyHandler();
+
     /**
      * Called when the value of {@link #mCurrentShift} changes
      */
+    @UiThread
     public abstract void updateFinalShift();
 
-
     /**
      * Called when motion pause is detected
      */
@@ -150,15 +265,39 @@
     @UiThread
     public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
 
-    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) { }
+    public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState);
 
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
 
-    public void initWhenReady() { }
+    public void initWhenReady() {
+        // Preload the plan
+        mRecentsModel.getTasks(null);
+
+        mActivityInitListener.register();
+    }
+
+    /**
+     * Applies the transform on the recents animation without any additional null checks
+     */
+    protected void applyTransformUnchecked() {
+        float shift = mCurrentShift.value;
+        float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
+        float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
+                mClipAnimationHelper.getTargetRect().width());
+        mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
+        mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
+                mTransformParams);
+    }
+
+    private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
+        float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + mPageSpacing;
+        float interpolation = Math.min(1, offsetX / distanceToReachEdge);
+        return TaskView.getCurveScaleForInterpolation(interpolation);
+    }
 
     public interface Factory {
 
         BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask,
-                long touchTimeMs, boolean continuingLastGesture);
+                long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask);
     }
 }
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 fd4b394..4bc4c76 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -649,20 +649,17 @@
 
         final boolean shouldDefer;
         final BaseSwipeUpHandler.Factory factory;
-        final Intent homeIntent;
 
         if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
-            shouldDefer = true;
+            shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
             factory = mFallbackNoButtonFactory;
-            homeIntent = mOverviewComponentObserver.getHomeIntent();
         } else {
             shouldDefer = mOverviewComponentObserver.getActivityControlHelper()
                     .deferStartingActivity(mActiveNavBarRegion, event);
             factory = mWindowTreansformFactory;
-            homeIntent = mOverviewComponentObserver.getOverviewIntent();
         }
 
-        return new OtherActivityInputConsumer(this, runningTaskInfo, homeIntent,
+        return new OtherActivityInputConsumer(this, runningTaskInfo,
                 shouldDefer, mOverviewCallbacks, this::onConsumerInactive,
                 sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
                 disableHorizontalSwipe(event), factory);
@@ -809,16 +806,15 @@
     }
 
     private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
-            long touchTimeMs, boolean continuingLastGesture) {
-        return  new WindowTransformSwipeHandler(
-                runningTask, this, touchTimeMs,
-                mOverviewComponentObserver.getActivityControlHelper(),
-                continuingLastGesture, mInputConsumer, mRecentsModel);
+            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
+        return  new WindowTransformSwipeHandler(runningTask, this, touchTimeMs,
+                mOverviewComponentObserver, continuingLastGesture, mInputConsumer, mRecentsModel);
     }
 
     private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(RunningTaskInfo runningTask,
-            long touchTimeMs, boolean continuingLastGesture) {
-        return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask);
+            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
+        return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask,
+                mRecentsModel, mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
     }
 
     public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) {
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 c60b28d..184b8e1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -18,7 +18,6 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
-import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -32,7 +31,6 @@
 import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.HIDE;
 import static com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState.PEEK;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.HOME;
 import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.LAST_TASK;
@@ -48,14 +46,13 @@
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.View;
@@ -82,7 +79,6 @@
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.FloatingIconView;
-import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
 import com.android.quickstep.ActivityControlHelper.AnimationFactory;
 import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
@@ -99,14 +95,14 @@
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 import com.android.systemui.shared.system.WindowCallbacksCompat;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
 @TargetApi(Build.VERSION_CODES.O)
-public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> extends BaseSwipeUpHandler
+public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
+        extends BaseSwipeUpHandler<T>
         implements OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
@@ -219,35 +215,24 @@
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
     private boolean mIsShelfPeeking;
-    private DeviceProfile mDp;
 
     private boolean mContinuingLastGesture;
     // To avoid UI jump when gesture is started, we offset the animation by the threshold.
     private float mShiftAtGestureStart = 0;
 
-    private final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
-
-    private final ActivityControlHelper<T> mActivityControlHelper;
-    private final ActivityInitListener mActivityInitListener;
-    private final RecentsModel mRecentsModel;
-
-    private final SysUINavigationMode.Mode mMode;
+    private final Mode mMode;
 
     private final int mRunningTaskId;
     private ThumbnailData mTaskSnapshot;
 
-    private MultiStateCallback mStateCallback;
     // Used to control launcher components throughout the swipe gesture.
     private AnimatorPlaybackController mLauncherTransitionController;
     private boolean mHasLauncherTransitionControllerStarted;
 
-    private T mActivity;
     private AnimationFactory mAnimationFactory = (t) -> { };
     private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
 
-    private boolean mCanceled;
     private boolean mWasLauncherAlreadyVisible;
-    private int mFinishingRecentsAnimationForNewTaskId = -1;
 
     private boolean mPassedOverviewThreshold;
     private boolean mGestureStarted;
@@ -256,24 +241,17 @@
     private PointF mDownPos;
     private boolean mIsLikelyToStartNewTask;
 
-    private final RecentsAnimationWrapper mRecentsAnimationWrapper;
-
     private final long mTouchTimeMs;
     private long mLauncherFrameDrawnTime;
 
     public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
-            long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
+            long touchTimeMs, OverviewComponentObserver overviewComponentObserver,
+            boolean continuingLastGesture,
             InputConsumerController inputConsumer, RecentsModel recentsModel) {
-        super(context);
+        super(context, overviewComponentObserver, recentsModel, inputConsumer);
         mRunningTaskId = runningTaskInfo.id;
         mTouchTimeMs = touchTimeMs;
-        mActivityControlHelper = controller;
-        mRecentsModel = recentsModel;
-        mActivityInitListener = mActivityControlHelper
-                .createActivityInitListener(this::onActivityInit);
         mContinuingLastGesture = continuingLastGesture;
-        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
-                this::createNewInputProxyHandler);
 
         mMode = SysUINavigationMode.getMode(context);
         initStateCallbacks();
@@ -342,14 +320,6 @@
         }
     }
 
-    private void setStateOnUiThread(int stateFlag) {
-        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
-            mStateCallback.setState(stateFlag);
-        } else {
-            postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
-        }
-    }
-
     private Rect getStackBounds(DeviceProfile dp) {
         if (mActivity != null) {
             int loc[] = new int[2];
@@ -383,14 +353,7 @@
     }
 
     @Override
-    public void initWhenReady() {
-        // Preload the plan
-        mRecentsModel.getTasks(null);
-
-        mActivityInitListener.register();
-    }
-
-    private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+    protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
         if (mActivity == activity) {
             return true;
         }
@@ -411,19 +374,7 @@
         }
 
         mRecentsView = activity.getOverviewPanel();
-        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
-            mTransformParams.setSyncTransactionApplier(applier);
-            mRecentsAnimationWrapper.runOnInit(() ->
-                    mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
-            });
-
-        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
-            if (mGestureEndTarget != HOME) {
-                updateFinalShift();
-            }
-        });
-        mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
-        mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+        linkRecentsViewScroll();
         mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
         mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
 
@@ -438,6 +389,11 @@
         return true;
     }
 
+    @Override
+    protected boolean moveWindowWithRecentsScroll() {
+        return mGestureEndTarget != HOME;
+    }
+
     private void onLauncherStart(final T activity) {
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "onLauncherStart");
@@ -654,20 +610,18 @@
         updateLauncherTransitionProgress();
     }
 
-    @UiThread
+    @Override
+    public Intent getLaunchIntent() {
+        return mOverviewComponentObserver.getOverviewIntent();
+    }
+
     @Override
     public void updateFinalShift() {
-        float shift = mCurrentShift.value;
 
         SwipeAnimationTargetSet controller = mRecentsAnimationWrapper.getController();
         if (controller != null) {
-            float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
-            float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
-                    mClipAnimationHelper.getTargetRect().width());
-            mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
-            mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
-                    mTransformParams);
-            updateSysUiFlags(shift);
+            applyTransformUnchecked();
+            updateSysUiFlags(mCurrentShift.value);
         }
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -817,8 +771,8 @@
         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
-    @UiThread
-    private InputConsumer createNewInputProxyHandler() {
+    @Override
+    protected InputConsumer createNewInputProxyHandler() {
         endRunningWindowAnim(true /* cancel */);
         endLauncherTransitionController();
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
@@ -1208,40 +1162,15 @@
 
     @UiThread
     private void startNewTask() {
-        // Launch the task user scrolled to (mRecentsView.getNextPage()).
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // We finish recents animation inside launchTask() when live tile is enabled.
-            mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */,
-                    true /* freezeTaskList */);
-        } else {
-            int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id;
-            mFinishingRecentsAnimationForNewTaskId = taskId;
-            mRecentsAnimationWrapper.finish(true /* toRecents */, () -> {
-                if (!mCanceled) {
-                    TaskView nextTask = mRecentsView.getTaskView(taskId);
-                    if (nextTask != null) {
-                        nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
-                                success -> {
-                            if (!success) {
-                                // We couldn't launch the task, so take user to overview so they can
-                                // decide what to do instead of staying in this broken state.
-                                endLauncherTransitionController();
-                                mActivityControlHelper.onLaunchTaskFailed(mActivity);
-                                nextTask.notifyTaskLaunchFailed(TAG);
-                                updateSysUiFlags(1 /* windowProgress == overview */);
-                            } else {
-                                mActivityControlHelper.onLaunchTaskSuccess(mActivity);
-                            }
-                        }, mMainThreadHandler);
-                        doLogGesture(NEW_TASK);
-                    }
-                    reset();
-                }
-                mCanceled = false;
-                mFinishingRecentsAnimationForNewTaskId = -1;
-            });
-        }
-        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
+        startNewTask(STATE_HANDLER_INVALIDATED, success -> {
+            if (!success) {
+                // We couldn't launch the task, so take user to overview so they can
+                // decide what to do instead of staying in this broken state.
+                endLauncherTransitionController();
+                updateSysUiFlags(1 /* windowProgress == overview */);
+            }
+            doLogGesture(NEW_TASK);
+        });
     }
 
     private void reset() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index c2876180..eaf6eba 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -88,6 +88,12 @@
     }
 
     @Override
+    public void reset() {
+        super.reset();
+        resetViewUI();
+    }
+
+    @Override
     protected void getTaskSize(DeviceProfile dp, Rect outRect) {
         LayoutUtils.calculateFallbackTaskSize(getContext(), dp, outRect);
     }
@@ -115,6 +121,12 @@
     }
 
     @Override
+    public void resetTaskVisuals() {
+        super.resetTaskVisuals();
+        setFullscreenProgress(mFullscreenProgress);
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
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 a5a8f38..a0e806e 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,15 +15,16 @@
  */
 package com.android.quickstep.inputconsumers;
 
+import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
 import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
+import static com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer.GestureEndTarget.NEW_TASK;
 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 android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
@@ -34,24 +35,53 @@
 import android.os.Bundle;
 import android.view.WindowManager;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
-import com.android.quickstep.ActivityControlHelper;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseSwipeUpHandler;
+import com.android.quickstep.MultiStateCallback;
 import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.ObjectWrapper;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
+import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
-public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler {
+public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler<RecentsActivity> {
+
+    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
+
+    private static int getFlagForIndex(int index, String name) {
+        if (DEBUG_STATES) {
+            STATE_NAMES[index] = name;
+        }
+        return 1 << index;
+    }
+
+    private static final int STATE_RECENTS_PRESENT =
+            getFlagForIndex(0, "STATE_RECENTS_PRESENT");
+    private static final int STATE_HANDLER_INVALIDATED =
+            getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
+
+    private static final int STATE_GESTURE_CANCELLED =
+            getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
+    private static final int STATE_GESTURE_COMPLETED =
+            getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
+    private static final int STATE_APP_CONTROLLER_RECEIVED =
+            getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
 
     public enum GestureEndTarget {
         HOME(3, 100, 1),
         RECENTS(1, 300, 0),
-        LAST_TASK(0, 150, 1);
+        LAST_TASK(0, 150, 1),
+        NEW_TASK(0, 150, 1);
 
         private final float mEndProgress;
         private final long mDurationMultiplier;
@@ -64,53 +94,131 @@
         }
     }
 
-    private final ActivityControlHelper mActivityControlHelper;
-    private final OverviewComponentObserver mOverviewComponentObserver;
     private final int mRunningTaskId;
 
-    private final DeviceProfile mDP;
     private final Rect mTargetRect = new Rect();
 
     private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
 
-    private SwipeAnimationTargetSet mSwipeAnimationTargetSet;
-
     private boolean mIsMotionPaused = false;
     private GestureEndTarget mEndTarget;
 
+    private final boolean mInQuickSwitchMode;
+    private final boolean mContinuingLastGesture;
+
+    private Animator mFinishAnimation;
+
     public FallbackNoButtonInputConsumer(Context context,
             OverviewComponentObserver overviewComponentObserver,
-            RunningTaskInfo runningTaskInfo) {
-        super(context);
-        mOverviewComponentObserver = overviewComponentObserver;
-        mActivityControlHelper = overviewComponentObserver.getActivityControlHelper();
+            RunningTaskInfo runningTaskInfo, RecentsModel recentsModel,
+            InputConsumerController inputConsumer,
+            boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
+        super(context, overviewComponentObserver, recentsModel, inputConsumer);
         mRunningTaskId = runningTaskInfo.id;
-        mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
+        mDp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
         mLauncherAlpha.value = 1;
 
+        mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
+        mContinuingLastGesture = continuingLastGesture;
         mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
+        initStateCallbacks();
         initTransitionTarget();
     }
 
+    private void initStateCallbacks() {
+        mStateCallback = new MultiStateCallback(STATE_NAMES);
+
+        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
+                this::onHandlerInvalidated);
+        mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
+                this::onHandlerInvalidatedWithRecents);
+
+        mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
+                this::finishAnimationTargetSetAnimationComplete);
+
+        if (mInQuickSwitchMode) {
+            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
+                            | STATE_RECENTS_PRESENT,
+                    this::finishAnimationTargetSet);
+        } else {
+            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
+                    this::finishAnimationTargetSet);
+        }
+    }
+
     private void onLauncherAlphaChanged() {
-        if (mSwipeAnimationTargetSet != null && mEndTarget == null) {
-            mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+        if (mRecentsAnimationWrapper.targetSet != null && mEndTarget == null) {
+            applyTransformUnchecked();
         }
     }
 
     @Override
+    protected boolean onActivityInit(final RecentsActivity activity, Boolean alreadyOnHome) {
+        mActivity = activity;
+        mRecentsView = activity.getOverviewPanel();
+        linkRecentsViewScroll();
+        mRecentsView.setDisallowScrollToClearAll(true);
+        mRecentsView.getClearAllButton().setVisibilityAlpha(0);
+
+        ((FallbackRecentsView) mRecentsView).setZoomProgress(1);
+
+        if (!mContinuingLastGesture) {
+            mRecentsView.onGestureAnimationStart(mRunningTaskId);
+        }
+        setStateOnUiThread(STATE_RECENTS_PRESENT);
+        return true;
+    }
+
+    @Override
+    protected boolean moveWindowWithRecentsScroll() {
+        return mInQuickSwitchMode;
+    }
+
+    @Override
+    public void initWhenReady() {
+        if (mInQuickSwitchMode) {
+            // Only init if we are in quickswitch mode
+            super.initWhenReady();
+        }
+    }
+
+    @Override
+    public void updateDisplacement(float displacement) {
+        if (!mInQuickSwitchMode) {
+            super.updateDisplacement(displacement);
+        }
+    }
+
+    @Override
+    protected InputConsumer createNewInputProxyHandler() {
+        // Just consume all input on the active task
+        return InputConsumer.NO_OP;
+    }
+
+    @Override
     public void onMotionPauseChanged(boolean isPaused) {
-        mIsMotionPaused = isPaused;
-        mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
-                .setDuration(150).start();
-        performHapticFeedback();
+        if (!mInQuickSwitchMode) {
+            mIsMotionPaused = isPaused;
+            mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
+                    .setDuration(150).start();
+            performHapticFeedback();
+        }
+    }
+
+    @Override
+    public Intent getLaunchIntent() {
+        if (mInQuickSwitchMode) {
+            return mOverviewComponentObserver.getOverviewIntent();
+        } else {
+            return mOverviewComponentObserver.getHomeIntent();
+        }
     }
 
     @Override
     public void updateFinalShift() {
         mTransformParams.setProgress(mCurrentShift.value);
-        if (mSwipeAnimationTargetSet != null) {
-            mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+        if (mRecentsAnimationWrapper.targetSet != null) {
+            applyTransformUnchecked();
         }
     }
 
@@ -118,41 +226,90 @@
     public void onGestureCancelled() {
         updateDisplacement(0);
         mEndTarget = LAST_TASK;
-        finishAnimationTargetSetAnimationComplete();
+        setStateOnUiThread(STATE_GESTURE_CANCELLED);
     }
 
     @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;
+        if (mInQuickSwitchMode) {
+            // For now set it to non-null, it will be reset before starting the animation
+            mEndTarget = LAST_TASK;
         } else {
-            mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
+            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 {
+                mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
+            }
         }
-        if (mSwipeAnimationTargetSet != null) {
-            finishAnimationTargetSet();
+        setStateOnUiThread(STATE_GESTURE_COMPLETED);
+    }
+
+    @Override
+    public void onConsumerAboutToBeSwitched(SwipeSharedState sharedState) {
+        if (mInQuickSwitchMode && mEndTarget != null) {
+            sharedState.canGestureBeContinued = true;
+            sharedState.goingToLauncher = false;
+
+            mCanceled = true;
+            mCurrentShift.cancelAnimation();
+            if (mFinishAnimation != null) {
+                mFinishAnimation.cancel();
+            }
+
+            if (mRecentsView != null) {
+                if (mFinishingRecentsAnimationForNewTaskId != -1) {
+                    TaskView newRunningTaskView = mRecentsView.getTaskView(
+                            mFinishingRecentsAnimationForNewTaskId);
+                    int newRunningTaskId = newRunningTaskView != null
+                            ? newRunningTaskView.getTask().key.id
+                            : -1;
+                    mRecentsView.setCurrentTask(newRunningTaskId);
+                    sharedState.setRecentsAnimationFinishInterrupted(newRunningTaskId);
+                }
+                mRecentsView.setOnScrollChangeListener(null);
+            }
+        } else {
+            setStateOnUiThread(STATE_HANDLER_INVALIDATED);
         }
     }
 
+
+    private void onHandlerInvalidated() {
+        mActivityInitListener.unregister();
+        if (mGestureEndCallback != null) {
+            mGestureEndCallback.run();
+        }
+    }
+
+    private void onHandlerInvalidatedWithRecents() {
+        mRecentsView.onGestureAnimationEnd();
+        mRecentsView.setDisallowScrollToClearAll(false);
+        mRecentsView.getClearAllButton().setVisibilityAlpha(1);
+    }
+
+
     private void finishAnimationTargetSetAnimationComplete() {
         switch (mEndTarget) {
             case HOME:
-                mSwipeAnimationTargetSet.finishController(true, null, true);
+                mRecentsAnimationWrapper.finish(true, null, true);
                 break;
             case LAST_TASK:
-                mSwipeAnimationTargetSet.finishController(false, null, false);
+                mRecentsAnimationWrapper.finish(false, null, false);
                 break;
             case RECENTS: {
                 ThumbnailData thumbnail =
-                        mSwipeAnimationTargetSet.controller.screenshotTask(mRunningTaskId);
-                mSwipeAnimationTargetSet.controller.setCancelWithDeferredScreenshot(true);
+                        mRecentsAnimationWrapper.targetSet.controller.screenshotTask(mRunningTaskId);
+                mRecentsAnimationWrapper.setCancelWithDeferredScreenshot(true);
 
                 ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+                ActivityOptionsCompat.setFreezeRecentTasksList(options);
+
                 Bundle extras = new Bundle();
                 extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
                 extras.putInt(EXTRA_TASK_ID, mRunningTaskId);
@@ -160,33 +317,58 @@
                 Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
                         .putExtras(extras);
                 mContext.startActivity(intent, options.toBundle());
-                mSwipeAnimationTargetSet.controller.cleanupScreenshot();
+                mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
+                break;
+            }
+            case NEW_TASK: {
+                startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
                 break;
             }
         }
-        if (mGestureEndCallback != null) {
-            mGestureEndCallback.run();
-        }
+
+        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
     private void finishAnimationTargetSet() {
+        if (mInQuickSwitchMode) {
+            // Recalculate the end target, some views might have been initialized after
+            // gesture has ended.
+            if (mRecentsView == null || !mRecentsAnimationWrapper.hasTargets()) {
+                mEndTarget = LAST_TASK;
+            } else {
+                final int runningTaskIndex = mRecentsView.getRunningTaskIndex();
+                final int taskToLaunch = mRecentsView.getNextPage();
+                mEndTarget = (runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
+                        ? NEW_TASK : LAST_TASK;
+            }
+        }
+
         float endProgress = mEndTarget.mEndProgress;
 
-        if (mCurrentShift.value != endProgress) {
+        if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
             AnimatorSet anim = new AnimatorSet();
             anim.play(mLauncherAlpha.animateToValue(
                     mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
             anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
 
-            anim.setDuration((long) (mEndTarget.mDurationMultiplier *
-                    Math.abs(endProgress - mCurrentShift.value)));
-            anim.addListener(new AnimatorListenerAdapter() {
+
+            long duration = (long) (mEndTarget.mDurationMultiplier *
+                    Math.abs(endProgress - mCurrentShift.value));
+            if (mRecentsView != null) {
+                duration = Math.max(duration, mRecentsView.getScroller().getDuration());
+            }
+
+            anim.setDuration(duration);
+            anim.addListener(new AnimationSuccessListener() {
+
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void onAnimationSuccess(Animator animator) {
                     finishAnimationTargetSetAnimationComplete();
+                    mFinishAnimation = null;
                 }
             });
             anim.start();
+            mFinishAnimation = anim;
         } else {
             finishAnimationTargetSetAnimationComplete();
         }
@@ -194,34 +376,36 @@
 
     @Override
     public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) {
-        mSwipeAnimationTargetSet = targetSet;
-        Rect overviewStackBounds = new Rect(0, 0, mDP.widthPx, mDP.heightPx);
+        mRecentsAnimationWrapper.setController(targetSet);
+        mRecentsAnimationWrapper.enableInputConsumer();
+        Rect overviewStackBounds = new Rect(0, 0, mDp.widthPx, mDp.heightPx);
         RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
 
-        mDP.updateIsSeascape(mContext.getSystemService(WindowManager.class));
+        mDp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
         if (targetSet.homeContentInsets != null) {
-            mDP.updateInsets(targetSet.homeContentInsets);
+            mDp.updateInsets(targetSet.homeContentInsets);
         }
 
         if (runningTaskTarget != null) {
             mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
         }
-        mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
+        mClipAnimationHelper.prepareAnimation(mDp, false /* isOpening */);
         initTransitionTarget();
-        mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+        applyTransformUnchecked();
 
-        if (mEndTarget != null) {
-            finishAnimationTargetSet();
-        }
+        setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
     }
 
     private void initTransitionTarget() {
         mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
-                mDP, mContext, mTargetRect);
-        mDragLengthFactor = (float) mDP.heightPx / mTransitionDragLength;
+                mDp, mContext, mTargetRect);
+        mDragLengthFactor = (float) mDp.heightPx / mTransitionDragLength;
         mClipAnimationHelper.updateTargetRect(mTargetRect);
     }
 
     @Override
-    public void onRecentsAnimationCanceled() { }
+    public void onRecentsAnimationCanceled() {
+        mRecentsAnimationWrapper.setController(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 9114995..86766d9 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
@@ -35,7 +35,6 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.content.ContextWrapper;
-import android.content.Intent;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.Build;
@@ -79,7 +78,6 @@
 
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final RunningTaskInfo mRunningTask;
-    private final Intent mHomeIntent;
     private final OverviewCallbacks mOverviewCallbacks;
     private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
@@ -117,12 +115,13 @@
     private float mStartDisplacement;
 
     private Handler mMainThreadHandler;
-    private Runnable mCancelRecentsAnimationRunnable = () ->
+    private Runnable mCancelRecentsAnimationRunnable = () -> {
         ActivityManagerWrapper.getInstance().cancelRecentsAnimation(
                 true /* restoreHomeStackPosition */);
+    };
 
     public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
-            Intent homeIntent, boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
+            boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
             Consumer<OtherActivityInputConsumer> onCompleteCallback,
             SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat,
             RectF swipeTouchRegion, boolean disableHorizontalSwipe,
@@ -131,7 +130,6 @@
 
         mMainThreadHandler = new Handler(Looper.getMainLooper());
         mRunningTask = runningTaskInfo;
-        mHomeIntent = homeIntent;
         mMode = SysUINavigationMode.getMode(base);
         mSwipeTouchRegion = swipeTouchRegion;
         mHandlerFactory = handlerFactory;
@@ -204,7 +202,7 @@
                 // Start the window animation on down to give more time for launcher to draw if the
                 // user didn't start the gesture over the back button
                 if (!mIsDeferredDownTarget) {
-                    startTouchTrackingForWindowAnimation(ev.getEventTime());
+                    startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
                 }
 
                 RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
@@ -253,6 +251,10 @@
                     }
                 }
 
+                float horizontalDist = Math.abs(displacementX);
+                float upDist = -displacement;
+                boolean isLikelyToStartNewTask = horizontalDist > upDist;
+
                 if (!mPassedPilferInputSlop) {
                     float displacementY = mLastPos.y - mDownPos.y;
                     if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
@@ -268,7 +270,8 @@
                         if (mIsDeferredDownTarget) {
                             // Deferred gesture, start the animation and gesture tracking once
                             // we pass the actual touch slop
-                            startTouchTrackingForWindowAnimation(ev.getEventTime());
+                            startTouchTrackingForWindowAnimation(
+                                    ev.getEventTime(), isLikelyToStartNewTask);
                         }
                         if (!mPassedWindowMoveSlop) {
                             mPassedWindowMoveSlop = true;
@@ -286,9 +289,6 @@
                     }
 
                     if (mMode == Mode.NO_BUTTON) {
-                        float horizontalDist = Math.abs(displacementX);
-                        float upDist = -displacement;
-                        boolean isLikelyToStartNewTask = horizontalDist > upDist;
                         mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement
                                 || isLikelyToStartNewTask);
                         mMotionPauseDetector.addPosition(displacement, ev.getEventTime());
@@ -320,12 +320,13 @@
         mInteractionHandler.onGestureStarted();
     }
 
-    private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
+    private void startTouchTrackingForWindowAnimation(
+            long touchTimeMs, boolean isLikelyToStartNewTask) {
         TOUCH_INTERACTION_LOG.addLog("startRecentsAnimation");
 
         RecentsAnimationListenerSet listenerSet = mSwipeSharedState.getActiveListener();
         final BaseSwipeUpHandler handler = mHandlerFactory.newHandler(mRunningTask, touchTimeMs,
-                listenerSet != null);
+                listenerSet != null, isLikelyToStartNewTask);
 
         mInteractionHandler = handler;
         handler.setGestureEndCallback(this::onInteractionGestureFinished);
@@ -340,7 +341,7 @@
             RecentsAnimationListenerSet newListenerSet =
                     mSwipeSharedState.newRecentsAnimationListenerSet();
             newListenerSet.addListener(handler);
-            startRecentsActivityAsync(mHomeIntent, newListenerSet);
+            startRecentsActivityAsync(handler.getLaunchIntent(), newListenerSet);
         }
     }
 
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 432f8a1..6bf3155 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
@@ -289,7 +289,7 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private float mContentAlpha = 1;
     @ViewDebug.ExportedProperty(category = "launcher")
-    private float mFullscreenProgress = 0;
+    protected float mFullscreenProgress = 0;
 
     // Keeps track of task id whose visual state should not be reset
     private int mIgnoreResetTaskId = -1;
@@ -604,6 +604,7 @@
             TaskView taskView = (TaskView) getChildAt(i);
             if (mIgnoreResetTaskId != taskView.getTask().key.id) {
                 taskView.resetVisualProperties();
+                taskView.setStableAlpha(mContentAlpha);
             }
         }
         if (mRunningTaskTileHidden) {