Merge "Fix overshoot velocity in 2-button landscape mode" into ub-launcher3-qt-r1-dev
diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 900b94e..917800f 100644
--- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -34,6 +34,8 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.launcher3.util.UiThreadHelper;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
@@ -137,6 +139,9 @@
         return sConnected;
     }
 
+    public static final LooperExecutor BACKGROUND_EXECUTOR =
+            new LooperExecutor(UiThreadHelper.getBackgroundLooper());
+
     private RecentsModel mRecentsModel;
     private OverviewComponentObserver mOverviewComponentObserver;
     private OverviewCommandHelper mOverviewCommandHelper;
@@ -180,4 +185,8 @@
         }
         return mMyBinder;
     }
+
+    public static boolean isInputMonitorInitialized() {
+        return true;
+    }
 }
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 332e0fa..5465480 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -23,6 +23,7 @@
     package="com.android.launcher3" >
 
     <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
+    <uses-permission android:name="android.permission.VIBRATE" />
 
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 3fe4bcf..f06b8a9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -168,6 +168,21 @@
     }
 
     @Override
+    protected void goToTargetState(LauncherState targetState, int logAction) {
+        if (mPeekAnim != null && mPeekAnim.isStarted()) {
+            // Don't jump to the target state until overview is no longer peeking.
+            mPeekAnim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    FlingAndHoldTouchController.super.goToTargetState(targetState, logAction);
+                }
+            });
+        } else {
+            super.goToTargetState(targetState, logAction);
+        }
+    }
+
+    @Override
     protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
         if (handlingOverviewAnim()) {
             // We don't want the state transition to all apps to animate overview,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
new file mode 100644
index 0000000..3032ca3
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static 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;
+
+import androidx.annotation.UiThread;
+
+/**
+ * Base class for swipe up handler with some utility methods
+ */
+@TargetApi(Build.VERSION_CODES.Q)
+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;
+    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
+    private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
+    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
+
+    // The distance needed to drag to reach the task size in recents.
+    protected int mTransitionDragLength;
+    // How much further we can drag past recents, as a factor of mTransitionDragLength.
+    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();
+
+    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 final ActivityInitListener mActivityInitListener;
+    protected final RecentsAnimationWrapper mRecentsAnimationWrapper;
+
+    protected T mActivity;
+    protected RecentsView mRecentsView;
+    protected DeviceProfile mDp;
+    private final int mPageSpacing;
+
+    protected Runnable mGestureEndCallback;
+
+    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);
+    }
+
+    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;
+        }
+        if (Settings.System.getInt(
+                mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) {
+            return;
+        }
+
+        VibrationEffect effect = createPredefined(EFFECT_CLICK);
+        if (effect == null) {
+            return;
+        }
+        BACKGROUND_EXECUTOR.execute(() -> mVibrator.vibrate(effect));
+    }
+
+    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) {
+            shift = mDragLengthFactor;
+        } else {
+            float translation = Math.max(displacement, 0);
+            shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+            if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
+                float pullbackProgress = Utilities.getProgress(shift,
+                        DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
+                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
+                shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
+                        * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
+            }
+        }
+
+        mCurrentShift.updateValue(shift);
+    }
+
+    public void setGestureEndCallback(Runnable gestureEndCallback) {
+        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
+     */
+    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 abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState);
+
+    public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
+
+    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, boolean isLikelyToStartNewTask);
+    }
+}
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 fc29a56..61767e5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -28,8 +28,10 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.app.ActivityOptions;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.view.View;
 
@@ -42,7 +44,9 @@
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsRootView;
 import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ObjectWrapper;
 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.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -54,6 +58,9 @@
  */
 public final class RecentsActivity extends BaseRecentsActivity {
 
+    public static final String EXTRA_THUMBNAIL = "thumbnailData";
+    public static final String EXTRA_TASK_ID = "taskID";
+
     private Handler mUiHandler = new Handler(Looper.getMainLooper());
     private RecentsRootView mRecentsRootView;
     private FallbackRecentsView mFallbackRecentsView;
@@ -79,6 +86,20 @@
     }
 
     @Override
+    protected void onNewIntent(Intent intent) {
+        int taskID = intent.getIntExtra(EXTRA_TASK_ID, 0);
+        IBinder thumbnail = intent.getExtras().getBinder(EXTRA_THUMBNAIL);
+        if (taskID != 0 && thumbnail instanceof ObjectWrapper) {
+            ThumbnailData thumbnailData = ((ObjectWrapper<ThumbnailData>) thumbnail).get();
+            mFallbackRecentsView.showCurrentTask(taskID);
+            mFallbackRecentsView.updateThumbnail(taskID, thumbnailData);
+        }
+        intent.removeExtra(EXTRA_TASK_ID);
+        intent.removeExtra(EXTRA_THUMBNAIL);
+        super.onNewIntent(intent);
+    }
+
+    @Override
     protected void onHandleConfigChanged() {
         super.onHandleConfigChanged();
         mRecentsRootView.setup();
@@ -178,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 bf9d531..4bc4c76 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -43,7 +43,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.RectF;
@@ -95,12 +94,12 @@
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.systemui.shared.system.RecentsAnimationListener;
 import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
 
 import java.io.FileDescriptor;
@@ -124,10 +123,6 @@
     public String nextArg() {
         return pollFirst().toLowerCase();
     }
-
-    public String nextArgExact() {
-        return pollFirst();
-    }
 }
 
 /**
@@ -231,12 +226,17 @@
     };
 
     private static boolean sConnected = false;
+    private static boolean sInputMonitorInitialized = false;
     private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState();
 
     public static boolean isConnected() {
         return sConnected;
     }
 
+    public static boolean isInputMonitorInitialized() {
+        return sInputMonitorInitialized;
+    }
+
     public static SwipeSharedState getSwipeSharedState() {
         return sSwipeSharedState;
     }
@@ -244,6 +244,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;
@@ -330,6 +335,7 @@
             mInputMonitorCompat.dispose();
             mInputMonitorCompat = null;
         }
+        sInputMonitorInitialized = false;
     }
 
     private void initInputMonitor() {
@@ -347,6 +353,7 @@
             Log.e(TAG, "Unable to create input monitor", e);
         }
         initTouchBounds();
+        sInputMonitorInitialized = true;
     }
 
     private int getNavbarSize(String resName) {
@@ -508,9 +515,6 @@
     }
 
     private void onInputEvent(InputEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "onInputEvent " + ev);
-        }
         if (!(ev instanceof MotionEvent)) {
             Log.e(TAG, "Unknown event " + ev);
             return;
@@ -628,10 +632,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);
         }
@@ -644,17 +644,25 @@
                 && 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;
+
+        if (mMode == Mode.NO_BUTTON && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+            shouldDefer = !sSwipeSharedState.recentsAnimationFinishInterrupted;
+            factory = mFallbackNoButtonFactory;
+        } else {
+            shouldDefer = mOverviewComponentObserver.getActivityControlHelper()
+                    .deferStartingActivity(mActiveNavBarRegion, event);
+            factory = mWindowTreansformFactory;
+        }
+
+        return new OtherActivityInputConsumer(this, runningTaskInfo,
+                shouldDefer, mOverviewCallbacks, this::onConsumerInactive,
                 sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion,
-                disableHorizontalSwipe(event));
+                disableHorizontalSwipe(event), factory);
     }
 
     private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) {
@@ -697,6 +705,10 @@
         if (!mIsUserUnlocked) {
             return;
         }
+        if (!mMode.hasGestures && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
+            // Prevent the overview from being started before the real home on first boot.
+            return;
+        }
 
         final ActivityControlHelper<BaseDraggingActivity> activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
@@ -714,11 +726,7 @@
         }
 
         // Pass null animation handler to indicate this start is preload.
-        BackgroundExecutor.get().submit(
-                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                        mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(),
-                        null /* assistDataReceiver */, null /* animationHandler */,
-                        null /* resultCallback */, null /* resultCallbackHandler */));
+        startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(), null);
     }
 
     @Override
@@ -796,4 +804,21 @@
                 break;
         }
     }
+
+    private BaseSwipeUpHandler createWindowTransformSwipeHandler(RunningTaskInfo runningTask,
+            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, boolean isLikelyToStartNewTask) {
+        return new FallbackNoButtonInputConsumer(this, mOverviewComponentObserver, runningTask,
+                mRecentsModel, mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
+    }
+
+    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 4f351ba..ec16d88 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;
@@ -43,24 +41,20 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 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.HapticFeedbackConstants;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
@@ -68,10 +62,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;
@@ -81,7 +71,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;
@@ -90,34 +79,31 @@
 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;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.inputconsumers.InputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
-import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.ClipAnimationHelper.TargetAlphaProvider;
 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;
 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 java.util.function.BiFunction;
-import java.util.function.Consumer;
+import androidx.annotation.NonNull;
+import androidx.annotation.UiThread;
 
 @TargetApi(Build.VERSION_CODES.O)
 public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
-        implements SwipeAnimationListener, OnApplyWindowInsetsListener {
+        extends BaseSwipeUpHandler<T>
+        implements OnApplyWindowInsetsListener {
     private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
 
     private static final Rect TEMP_RECT = new Rect();
@@ -220,64 +206,33 @@
     private static final long SHELF_ANIM_DURATION = 240;
     public static final long RECENTS_ATTACH_DURATION = 300;
 
-    // Start resisting when swiping past this factor of mTransitionDragLength.
-    private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
-    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
-    private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
-    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
-
     /**
      * Used as the page index for logging when we return to the last task at the end of the gesture.
      */
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
-    private final ClipAnimationHelper mClipAnimationHelper;
-    private final ClipAnimationHelper.TransformParams mTransformParams;
-
-    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;
-    // The distance needed to drag to reach the task size in recents.
-    private int mTransitionDragLength;
-    // How much further we can drag past recents, as a factor of mTransitionDragLength.
-    private float mDragLengthFactor = 1;
 
-    // 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;
 
-    private final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler();
-
-    private final Context mContext;
-    private final ActivityControlHelper<T> mActivityControlHelper;
-    private final ActivityInitListener mActivityInitListener;
-
-    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 RecentsView mRecentsView;
     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;
@@ -286,25 +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,
-            InputConsumerController inputConsumer) {
-        mContext = context;
+            long touchTimeMs, OverviewComponentObserver overviewComponentObserver,
+            boolean continuingLastGesture,
+            InputConsumerController inputConsumer, RecentsModel recentsModel) {
+        super(context, overviewComponentObserver, recentsModel, inputConsumer);
         mRunningTaskId = runningTaskInfo.id;
         mTouchTimeMs = touchTimeMs;
-        mActivityControlHelper = controller;
-        mActivityInitListener = mActivityControlHelper
-                .createActivityInitListener(this::onActivityInit);
         mContinuingLastGesture = continuingLastGesture;
-        mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer,
-                this::createNewInputProxyHandler);
-        mClipAnimationHelper = new ClipAnimationHelper(context);
-        mTransformParams = new ClipAnimationHelper.TransformParams();
 
         mMode = SysUINavigationMode.getMode(context);
         initStateCallbacks();
@@ -373,11 +320,15 @@
         }
     }
 
-    private void setStateOnUiThread(int stateFlag) {
-        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
-            mStateCallback.setState(stateFlag);
+    private Rect getStackBounds(DeviceProfile dp) {
+        if (mActivity != null) {
+            int loc[] = new int[2];
+            View rootView = mActivity.getRootView();
+            rootView.getLocationOnScreen(loc);
+            return new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
+                    loc[1] + rootView.getHeight());
         } else {
-            postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
+            return new Rect(0, 0, dp.widthPx, dp.heightPx);
         }
     }
 
@@ -387,6 +338,13 @@
         Rect tempRect = new Rect();
         mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
                 dp, mContext, tempRect);
+        if (!dp.isMultiWindowMode) {
+            // When updating the target rect, also update the home bounds since the location on
+            // screen of the launcher window may be stale (position is not updated until first
+            // traversal after the window is resized).  We only do this for non-multiwindow because
+            // we otherwise use the minimized home bounds provided by the system.
+            mClipAnimationHelper.updateHomeBounds(getStackBounds(dp));
+        }
         mClipAnimationHelper.updateTargetRect(tempRect);
         if (mMode == Mode.NO_BUTTON) {
             // We can drag all the way to the top of the screen.
@@ -394,23 +352,8 @@
         }
     }
 
-    private long getFadeInDuration() {
-        if (mCurrentShift.getCurrentAnimation() != null) {
-            ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
-            long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
-
-            // TODO: Find a better heuristic
-            return Math.min(MAX_SWIPE_DURATION, Math.max(theirDuration, MIN_SWIPE_DURATION));
-        } else {
-            return MAX_SWIPE_DURATION;
-        }
-    }
-
-    public void initWhenReady() {
-        mActivityInitListener.register();
-    }
-
-    private boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
+    @Override
+    protected boolean onActivityInit(final T activity, Boolean alreadyOnHome) {
         if (mActivity == activity) {
             return true;
         }
@@ -431,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);
 
@@ -458,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");
@@ -577,30 +513,7 @@
         return TaskView.getCurveScaleForInterpolation(interpolation);
     }
 
-    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;
-        if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) {
-            mCurrentShift.updateValue(mDragLengthFactor);
-        } else {
-            float translation = Math.max(displacement, 0);
-            float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
-            if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) {
-                float pullbackProgress = Utilities.getProgress(shift,
-                        DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor);
-                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
-                shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress
-                        * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK);
-            }
-            mCurrentShift.updateValue(shift);
-        }
-    }
-
+    @Override
     public void onMotionPauseChanged(boolean isPaused) {
         setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
     }
@@ -651,6 +564,7 @@
         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
     }
 
+    @Override
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
         if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
             mIsLikelyToStartNewTask = isLikelyToStartNewTask;
@@ -666,9 +580,8 @@
         if (mIsShelfPeeking != wasShelfPeeking) {
             maybeUpdateRecentsAttachedState();
         }
-        if (mRecentsView != null && shelfState.shouldPreformHaptic) {
-            mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
-                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+        if (shelfState.shouldPreformHaptic) {
+            performHapticFeedback();
         }
     }
 
@@ -697,19 +610,18 @@
         updateLauncherTransitionProgress();
     }
 
-    @UiThread
-    private void updateFinalShift() {
-        float shift = mCurrentShift.value;
+    @Override
+    public Intent getLaunchIntent() {
+        return mOverviewComponentObserver.getOverviewIntent();
+    }
+
+    @Override
+    public void updateFinalShift() {
 
         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()) {
@@ -722,9 +634,8 @@
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
         if (passed != mPassedOverviewThreshold) {
             mPassedOverviewThreshold = passed;
-            if (mRecentsView != null && mMode != Mode.NO_BUTTON) {
-                mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
-                    HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+            if (mMode != Mode.NO_BUTTON) {
+                performHapticFeedback();
             }
         }
 
@@ -776,21 +687,12 @@
                     .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget);
             dp = dp.getMultiWindowProfile(mContext, new Point(
                     targetSet.minimizedHomeBounds.width(), targetSet.minimizedHomeBounds.height()));
-            dp.updateInsets(targetSet.homeContentInsets);
         } else {
-            if (mActivity != null) {
-                int loc[] = new int[2];
-                View rootView = mActivity.getRootView();
-                rootView.getLocationOnScreen(loc);
-                overviewStackBounds = new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(),
-                        loc[1] + rootView.getHeight());
-            } else {
-                overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
-            }
             // If we are not in multi-window mode, home insets should be same as system insets.
             dp = dp.copy(mContext);
-            dp.updateInsets(targetSet.homeContentInsets);
+            overviewStackBounds = getStackBounds(dp);
         }
+        dp.updateInsets(targetSet.homeContentInsets);
         dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
 
         if (runningTaskTarget != null) {
@@ -814,7 +716,7 @@
         TOUCH_INTERACTION_LOG.addLog("cancelRecentsAnimation");
     }
 
-    @UiThread
+    @Override
     public void onGestureStarted() {
         notifyGestureStartedAsync();
         mShiftAtGestureStart = mCurrentShift.value;
@@ -838,7 +740,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);
@@ -851,7 +753,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);
@@ -869,9 +771,9 @@
         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
-    @UiThread
-    private InputConsumer createNewInputProxyHandler() {
-        endRunningWindowAnim();
+    @Override
+    protected InputConsumer createNewInputProxyHandler() {
+        endRunningWindowAnim(true /* cancel */);
         endLauncherTransitionController();
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // Hide the task view, if not already hidden
@@ -883,9 +785,13 @@
                 ? InputConsumer.NO_OP : new OverviewInputConsumer(activity, null, true);
     }
 
-    private void endRunningWindowAnim() {
+    private void endRunningWindowAnim(boolean cancel) {
         if (mRunningWindowAnim != null) {
-            mRunningWindowAnim.end();
+            if (cancel) {
+                mRunningWindowAnim.cancel();
+            } else {
+                mRunningWindowAnim.end();
+            }
         }
     }
 
@@ -1177,27 +1083,37 @@
         // We want the window alpha to be 0 once this threshold is met, so that the
         // FolderIconView can be seen morphing into the icon shape.
         final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
-        anim.addOnUpdateListener((currentRect, progress) -> {
-            homeAnim.setPlayFraction(progress);
+        anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() {
+            @Override
+            public void onUpdate(RectF currentRect, float progress) {
+                homeAnim.setPlayFraction(progress);
 
-            float alphaProgress = ACCEL_1_5.getInterpolation(progress);
-            float windowAlpha = Utilities.boundToRange(Utilities.mapToRange(alphaProgress, 0,
-                    windowAlphaThreshold, 1.5f, 0f, Interpolators.LINEAR), 0, 1);
-            mTransformParams.setProgress(progress)
-                    .setCurrentRectAndTargetAlpha(currentRect, windowAlpha);
-            if (isFloatingIconView) {
-                mTransformParams.setCornerRadius(endRadius * progress + startRadius
-                        * (1f - progress));
-            }
-            mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
-                    false /* launcherOnTop */);
+                float alphaProgress = ACCEL_1_5.getInterpolation(progress);
+                float windowAlpha = Utilities.boundToRange(Utilities.mapToRange(alphaProgress, 0,
+                        windowAlphaThreshold, 1.5f, 0f, Interpolators.LINEAR), 0, 1);
+                mTransformParams.setProgress(progress)
+                        .setCurrentRectAndTargetAlpha(currentRect, windowAlpha);
+                if (isFloatingIconView) {
+                    mTransformParams.setCornerRadius(endRadius * progress + startRadius
+                            * (1f - progress));
+                }
+                mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
+                        false /* launcherOnTop */);
 
-            if (isFloatingIconView) {
-                ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
-                        windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false);
+                if (isFloatingIconView) {
+                    ((FloatingIconView) floatingView).update(currentRect, 1f, progress,
+                            windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false);
+                }
+
+                updateSysUiFlags(Math.max(progress, mCurrentShift.value));
             }
 
-            updateSysUiFlags(Math.max(progress, mCurrentShift.value));
+            @Override
+            public void onCancel() {
+                if (isFloatingIconView) {
+                    ((FloatingIconView) floatingView).fastFinish();
+                }
+            }
         });
         anim.addAnimatorListener(new AnimationSuccessListener() {
             @Override
@@ -1222,11 +1138,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
@@ -1239,43 +1162,18 @@
 
     @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);
+        });
     }
 
-    public void reset() {
+    private void reset() {
         setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
@@ -1283,7 +1181,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
@@ -1306,7 +1204,7 @@
     }
 
     private void invalidateHandler() {
-        endRunningWindowAnim();
+        endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
             mGestureEndCallback.run();
@@ -1446,17 +1344,12 @@
         reset();
     }
 
-    public void setGestureEndCallback(Runnable gestureEndCallback) {
-        mGestureEndCallback = gestureEndCallback;
-    }
-
-    private void setTargetAlphaProvider(
-            BiFunction<RemoteAnimationTargetCompat, Float, Float> provider) {
+    private void setTargetAlphaProvider(TargetAlphaProvider provider) {
         mClipAnimationHelper.setTaskAlphaCallback(provider);
         updateFinalShift();
     }
 
-    public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
+    public static float getHiddenTargetAlpha(RemoteAnimationTargetCompat app, float expectedAlpha) {
         if (!isNotInRecents(app)) {
             return 0;
         }
@@ -1471,12 +1364,34 @@
     private interface RunningWindowAnim {
         void end();
 
+        void cancel();
+
         static RunningWindowAnim wrap(Animator animator) {
-            return animator::end;
+            return new RunningWindowAnim() {
+                @Override
+                public void end() {
+                    animator.end();
+                }
+
+                @Override
+                public void cancel() {
+                    animator.cancel();
+                }
+            };
         }
 
         static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
-            return rectFSpringAnim::end;
+            return new RunningWindowAnim() {
+                @Override
+                public void end() {
+                    rectFSpringAnim.end();
+                }
+
+                @Override
+                public void cancel() {
+                    rectFSpringAnim.cancel();
+                }
+            };
         }
     }
 }
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/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index db2af59..3d763ab 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -44,8 +45,6 @@
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.RecentsAnimationListenerSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputMonitorCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -209,9 +208,7 @@
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
 
         mInputMonitorCompat.pilferPointers();
-        BackgroundExecutor.get().submit(
-                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                        intent, null, newListenerSet, null, null));
+        startRecentsActivityAsync(intent, newListenerSet);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
index d05ca2a..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,344 +15,397 @@
  */
 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.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
+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.WindowTransformSwipeHandler.MIN_SWIPE_DURATION;
-import static com.android.quickstep.inputconsumers.OtherActivityInputConsumer.QUICKSTEP_TOUCH_SLOP_RATIO;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+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.ValueAnimator;
+import android.animation.AnimatorSet;
 import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
+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.util.ClipAnimationHelper;
-import com.android.quickstep.util.ClipAnimationHelper.TransformParams;
-import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsAnimationListenerSet;
+import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.ObjectWrapper;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
-import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
-import com.android.systemui.shared.system.InputMonitorCompat;
+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 implements InputConsumer, SwipeAnimationListener {
+public class FallbackNoButtonInputConsumer extends BaseSwipeUpHandler<RecentsActivity> {
 
-    private static final int STATE_NOT_FINISHED = 0;
-    private static final int STATE_FINISHED_TO_HOME = 1;
-    private static final int STATE_FINISHED_TO_APP = 2;
+    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
 
-    private static final float PROGRESS_TO_END_GESTURE = -2;
+    private static int getFlagForIndex(int index, String name) {
+        if (DEBUG_STATES) {
+            STATE_NAMES[index] = name;
+        }
+        return 1 << index;
+    }
 
-    private final ActivityControlHelper mActivityControlHelper;
-    private final InputMonitorCompat mInputMonitor;
-    private final Context mContext;
-    private final NavBarPosition mNavBarPosition;
-    private final SwipeSharedState mSwipeSharedState;
-    private final OverviewComponentObserver mOverviewComponentObserver;
+    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),
+        NEW_TASK(0, 150, 1);
+
+        private final float mEndProgress;
+        private final long mDurationMultiplier;
+        private final float mLauncherAlpha;
+
+        GestureEndTarget(float endProgress, long durationMultiplier, float launcherAlpha) {
+            mEndProgress = endProgress;
+            mDurationMultiplier = durationMultiplier;
+            mLauncherAlpha = launcherAlpha;
+        }
+    }
+
     private final int mRunningTaskId;
 
-    private final ClipAnimationHelper mClipAnimationHelper;
-    private final TransformParams mTransformParams = new TransformParams();
-    private final float mTransitionDragLength;
-    private final DeviceProfile mDP;
+    private final Rect mTargetRect = new Rect();
 
-    private final RectF mSwipeTouchRegion;
-    private final boolean mDisableHorizontalSwipe;
+    private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
 
-    private final PointF mDownPos = new PointF();
-    private final PointF mLastPos = new PointF();
+    private boolean mIsMotionPaused = false;
+    private GestureEndTarget mEndTarget;
 
-    private int mActivePointerId = -1;
-    // Slop used to determine when we say that the gesture has started.
-    private boolean mPassedPilferInputSlop;
+    private final boolean mInQuickSwitchMode;
+    private final boolean mContinuingLastGesture;
 
-    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 float mProgress;
-
-    private int mState = STATE_NOT_FINISHED;
+    private Animator mFinishAnimation;
 
     public FallbackNoButtonInputConsumer(Context context,
-            ActivityControlHelper activityControlHelper, InputMonitorCompat inputMonitor,
-            SwipeSharedState swipeSharedState, RectF swipeTouchRegion,
             OverviewComponentObserver overviewComponentObserver,
-            boolean disableHorizontalSwipe, RunningTaskInfo runningTaskInfo) {
-        mContext = context;
-        mActivityControlHelper = activityControlHelper;
-        mInputMonitor = inputMonitor;
-        mOverviewComponentObserver = overviewComponentObserver;
+            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);
+        mLauncherAlpha.value = 1;
 
-        mSwipeSharedState = swipeSharedState;
-        mSwipeTouchRegion = swipeTouchRegion;
-        mDisableHorizontalSwipe = disableHorizontalSwipe;
-
-        mNavBarPosition = new NavBarPosition(context);
-        mVelocityTracker = VelocityTracker.obtain();
-
-        mTouchSlop = QUICKSTEP_TOUCH_SLOP_RATIO
-                * ViewConfiguration.get(context).getScaledTouchSlop();
-
-        mClipAnimationHelper = new ClipAnimationHelper(context);
-
-        mDP = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context).copy(context);
-        Rect tempRect = new Rect();
-        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
-                mDP, context, tempRect);
-        mClipAnimationHelper.updateTargetRect(tempRect);
+        mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
+        mContinuingLastGesture = continuingLastGesture;
+        mClipAnimationHelper.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
+        initStateCallbacks();
+        initTransitionTarget();
     }
 
-    @Override
-    public int getType() {
-        return TYPE_FALLBACK_NO_BUTTON;
-    }
+    private void initStateCallbacks() {
+        mStateCallback = new MultiStateCallback(STATE_NAMES);
 
-    @Override
-    public void onMotionEvent(MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            return;
-        }
+        mStateCallback.addCallback(STATE_HANDLER_INVALIDATED,
+                this::onHandlerInvalidated);
+        mStateCallback.addCallback(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
+                this::onHandlerInvalidatedWithRecents);
 
-        mVelocityTracker.addMovement(ev);
-        if (ev.getActionMasked() == ACTION_POINTER_UP) {
-            mVelocityTracker.clear();
-        }
+        mStateCallback.addCallback(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
+                this::finishAnimationTargetSetAnimationComplete);
 
-        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);
-                }
-                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();
-        BackgroundExecutor.get().submit(
-                () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                        homeIntent, null, listenerSet, null, null));
-
-        ActivityManagerWrapper.getInstance().closeSystemWindows(
-                CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-        mInputMonitor.pilferPointers();
-    }
-
-    private void updateDisplacement(float displacement) {
-        mProgress = displacement / mTransitionDragLength;
-        mTransformParams.setProgress(mProgress);
-
-        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);
-    }
-
-    /**
-     * 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) {
-            mState = STATE_FINISHED_TO_APP;
+        if (mInQuickSwitchMode) {
+            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
+                            | STATE_RECENTS_PRESENT,
+                    this::finishAnimationTargetSet);
         } 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;
+            mStateCallback.addCallback(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
+                    this::finishAnimationTargetSet);
+        }
+    }
+
+    private void onLauncherAlphaChanged() {
+        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) {
+        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 (mRecentsAnimationWrapper.targetSet != null) {
+            applyTransformUnchecked();
+        }
+    }
+
+    @Override
+    public void onGestureCancelled() {
+        updateDisplacement(0);
+        mEndTarget = LAST_TASK;
+        setStateOnUiThread(STATE_GESTURE_CANCELLED);
+    }
+
+    @Override
+    public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
+        if (mInQuickSwitchMode) {
+            // For now set it to non-null, it will be reset before starting the animation
+            mEndTarget = LAST_TASK;
+        } else {
             float flingThreshold = mContext.getResources()
                     .getDimension(R.dimen.quickstep_fling_threshold_velocity);
-            boolean isFling = Math.abs(velocity) > flingThreshold;
+            boolean isFling = Math.abs(endVelocity) > flingThreshold;
 
-            boolean goingHome;
-            if (!isFling) {
-                goingHome = -mProgress >= MIN_PROGRESS_FOR_OVERVIEW;
+            if (isFling) {
+                mEndTarget = endVelocity < 0 ? HOME : LAST_TASK;
+            } else if (mIsMotionPaused) {
+                mEndTarget = RECENTS;
             } else {
-                goingHome = velocity < 0;
+                mEndTarget = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? HOME : LAST_TASK;
+            }
+        }
+        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 (goingHome) {
-                mState = STATE_FINISHED_TO_HOME;
-            } else {
-                mState = STATE_FINISHED_TO_APP;
+            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:
+                mRecentsAnimationWrapper.finish(true, null, true);
+                break;
+            case LAST_TASK:
+                mRecentsAnimationWrapper.finish(false, null, false);
+                break;
+            case RECENTS: {
+                ThumbnailData thumbnail =
+                        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);
+
+                Intent intent = new Intent(mOverviewComponentObserver.getOverviewIntent())
+                        .putExtras(extras);
+                mContext.startActivity(intent, options.toBundle());
+                mRecentsAnimationWrapper.targetSet.controller.cleanupScreenshot();
+                break;
+            }
+            case NEW_TASK: {
+                startNewTask(STATE_HANDLER_INVALIDATED, b -> {});
+                break;
             }
         }
 
-        if (mSwipeAnimationTargetSet != null) {
-            finishAnimationTargetSet();
-        }
+        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
     private void finishAnimationTargetSet() {
-        if (mState == STATE_FINISHED_TO_APP) {
-            mSwipeAnimationTargetSet.finishController(false, null, false);
-        } else {
-            if (mProgress < PROGRESS_TO_END_GESTURE) {
-                mSwipeAnimationTargetSet.finishController(true, null, true);
+        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 {
-                long duration = (long) (Math.min(mProgress - PROGRESS_TO_END_GESTURE, 1)
-                        * MAX_SWIPE_DURATION / Math.abs(PROGRESS_TO_END_GESTURE));
-                if (duration < 0) {
-                    duration = MIN_SWIPE_DURATION;
-                }
-
-                ValueAnimator anim = ValueAnimator.ofFloat(mProgress, PROGRESS_TO_END_GESTURE);
-                anim.addUpdateListener(a -> {
-                    float p = (Float) anim.getAnimatedValue();
-                    mTransformParams.setProgress(p);
-                    mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
-                });
-                anim.setDuration(duration);
-                anim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mSwipeAnimationTargetSet.finishController(true, null, true);
-                    }
-                });
-                anim.start();
+                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 || mInQuickSwitchMode) {
+            AnimatorSet anim = new AnimatorSet();
+            anim.play(mLauncherAlpha.animateToValue(
+                    mLauncherAlpha.value, mEndTarget.mLauncherAlpha));
+            anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
+
+
+            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 onAnimationSuccess(Animator animator) {
+                    finishAnimationTargetSetAnimationComplete();
+                    mFinishAnimation = null;
+                }
+            });
+            anim.start();
+            mFinishAnimation = anim;
+        } else {
+            finishAnimationTargetSetAnimationComplete();
+        }
     }
 
     @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);
+        }
+
         if (runningTaskTarget != null) {
             mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
         }
-        mClipAnimationHelper.prepareAnimation(mDP, false /* isOpening */);
+        mClipAnimationHelper.prepareAnimation(mDp, false /* isOpening */);
+        initTransitionTarget();
+        applyTransformUnchecked();
 
-        overviewStackBounds
-                .inset(-overviewStackBounds.width() / 5, -overviewStackBounds.height() / 5);
-        mClipAnimationHelper.updateTargetRect(overviewStackBounds);
-        mClipAnimationHelper.applyTransform(mSwipeAnimationTargetSet, mTransformParams);
+        setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
+    }
 
-        if (mState != STATE_NOT_FINISHED) {
-            finishAnimationTargetSet();
-        }
+    private void initTransitionTarget() {
+        mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength(
+                mDp, mContext, mTargetRect);
+        mDragLengthFactor = (float) mDp.heightPx / mTransitionDragLength;
+        mClipAnimationHelper.updateTargetRect(mTargetRect);
     }
 
     @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;
+    public void onRecentsAnimationCanceled() {
+        mRecentsAnimationWrapper.setController(null);
+        setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 }
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 4c137d3..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
@@ -28,13 +28,13 @@
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
+import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.annotation.TargetApi;
 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;
@@ -48,21 +48,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.BackgroundExecutor;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 import java.util.function.Consumer;
@@ -83,16 +78,14 @@
 
     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 +93,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 +107,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;
 
@@ -128,20 +121,18 @@
     };
 
     public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
-            RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
-            InputConsumerController inputConsumer,
             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 +141,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 +152,7 @@
         float slop = QUICKSTEP_TOUCH_SLOP_RATIO * mTouchSlop;
         mSquaredTouchSlop = slop * slop;
 
-        mPassedPilferInputSlop = mPaddedWindowMoveSlop = continuingPreviousGesture;
+        mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
     }
 
@@ -186,7 +175,7 @@
         }
 
         // Proxy events to recents view
-        if (mPaddedWindowMoveSlop && mInteractionHandler != null
+        if (mPassedWindowMoveSlop && mInteractionHandler != null
                 && !mRecentsViewDispatcher.hasConsumer()) {
             mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
                     mNavBarPosition.getRotationMode()));
@@ -213,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);
@@ -251,17 +240,21 @@
                 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);
                         }
                     }
                 }
 
+                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) {
@@ -277,10 +270,11 @@
                         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 (!mPaddedWindowMoveSlop) {
-                            mPaddedWindowMoveSlop = true;
+                        if (!mPassedWindowMoveSlop) {
+                            mPassedWindowMoveSlop = true;
                             mStartDisplacement = Math.min(displacement, -mTouchSlop);
 
                         }
@@ -289,15 +283,12 @@
                 }
 
                 if (mInteractionHandler != null) {
-                    if (mPaddedWindowMoveSlop) {
+                    if (mPassedWindowMoveSlop) {
                         // Move
                         mInteractionHandler.updateDisplacement(displacement - mStartDisplacement);
                     }
 
                     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());
@@ -329,16 +320,14 @@
         mInteractionHandler.onGestureStarted();
     }
 
-    private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
+    private void startTouchTrackingForWindowAnimation(
+            long touchTimeMs, boolean isLikelyToStartNewTask) {
         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, isLikelyToStartNewTask);
 
-        // Preload the plan
-        mRecentsModel.getTasks(null);
         mInteractionHandler = handler;
         handler.setGestureEndCallback(this::onInteractionGestureFinished);
         mMotionPauseDetector.setOnMotionPauseListener(handler::onMotionPauseChanged);
@@ -352,9 +341,7 @@
             RecentsAnimationListenerSet newListenerSet =
                     mSwipeSharedState.newRecentsAnimationListenerSet();
             newListenerSet.addListener(handler);
-            BackgroundExecutor.get().submit(
-                    () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
-                            mHomeIntent, null, newListenerSet, null, null));
+            startRecentsActivityAsync(handler.getLaunchIntent(), newListenerSet);
         }
     }
 
@@ -366,7 +353,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 {
@@ -409,14 +396,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);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index f2e53bb..e553891 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -82,9 +82,6 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "onMotionEvent " + ev);
-        }
         if (!mProxyTouch) {
             return;
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index de671e0..cae273a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -99,8 +99,8 @@
     // Whether to boost the opening animation target layers, or the closing
     private int mBoostModeTargetLayers = -1;
 
-    private BiFunction<RemoteAnimationTargetCompat, Float, Float> mTaskAlphaCallback =
-            (t, a1) -> a1;
+    private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
+    private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
 
     public ClipAnimationHelper(Context context) {
         mWindowCornerRadius = getWindowCornerRadius(context.getResources());
@@ -119,8 +119,12 @@
     }
 
     public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
-        mHomeStackBounds.set(homeStackBounds);
         updateSourceStack(target);
+        updateHomeBounds(homeStackBounds);
+    }
+
+    public void updateHomeBounds(Rect homeStackBounds) {
+        mHomeStackBounds.set(homeStackBounds);
     }
 
     public void updateTargetRect(Rect targetRect) {
@@ -187,12 +191,12 @@
             Rect crop = mTmpRect;
             crop.set(app.sourceContainerBounds);
             crop.offsetTo(0, 0);
-            float alpha = 1f;
+            float alpha;
             int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
             float cornerRadius = 0f;
             float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width();
             if (app.mode == targetSet.targetMode) {
-                alpha = mTaskAlphaCallback.apply(app, params.targetAlpha);
+                alpha = mTaskAlphaCallback.getAlpha(app, params.targetAlpha);
                 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
                     mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL);
                     mTmpMatrix.postTranslate(app.position.x, app.position.y);
@@ -214,9 +218,12 @@
                     // home target.
                     alpha = 1 - (progress * params.targetAlpha);
                 }
-            } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
-                crop = null;
-                layer = Integer.MAX_VALUE;
+            } else {
+                alpha = mBaseAlphaCallback.getAlpha(app, progress);
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && launcherOnTop) {
+                    crop = null;
+                    layer = Integer.MAX_VALUE;
+                }
             }
 
             // Since radius is in Surface space, but we draw the rounded corners in screen space, we
@@ -247,11 +254,14 @@
         }
     }
 
-    public void setTaskAlphaCallback(
-            BiFunction<RemoteAnimationTargetCompat, Float, Float> callback) {
+    public void setTaskAlphaCallback(TargetAlphaProvider callback) {
         mTaskAlphaCallback = callback;
     }
 
+    public void setBaseAlphaCallback(TargetAlphaProvider callback) {
+        mBaseAlphaCallback = callback;
+    }
+
     public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
         fromTaskThumbnailView(ttv, rv, null);
     }
@@ -357,6 +367,10 @@
         return mCurrentCornerRadius;
     }
 
+    public interface TargetAlphaProvider {
+        float getAlpha(RemoteAnimationTargetCompat target, float expectedAlpha);
+    }
+
     public static class TransformParams {
         float progress;
         public float offsetX;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
new file mode 100644
index 0000000..abfe3ad
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ObjectWrapper.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Utility class to pass non-parcealable objects within same process using parcealable payload.
+ *
+ * It wraps the object in a binder as binders are singleton within a process
+ */
+public class ObjectWrapper<T> extends Binder {
+
+    private final T mObject;
+
+    public ObjectWrapper(T object) {
+        mObject = object;
+    }
+
+    public T get() {
+        return mObject;
+    }
+
+    public static IBinder wrap(Object obj) {
+        return new ObjectWrapper<>(obj);
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index 77dc6f3..9c5cf20 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -225,7 +225,18 @@
         }
     }
 
+    public void cancel() {
+        if (mAnimsStarted) {
+            for (OnUpdateListener onUpdateListener : mOnUpdateListeners) {
+                onUpdateListener.onCancel();
+            }
+        }
+        end();
+    }
+
     public interface OnUpdateListener {
         void onUpdate(RectF currentRect, float progress);
+
+        void onCancel();
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 07e9686..1705c97 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -16,6 +16,8 @@
 package com.android.quickstep.util;
 
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.view.View;
 import android.view.ViewGroup;
@@ -29,6 +31,7 @@
 import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.SpringObjectAnimator;
@@ -79,9 +82,19 @@
                 .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);
 
         DeviceProfile grid = launcher.getDeviceProfile();
-        ShortcutAndWidgetContainer currentPage = ((CellLayout) launcher.getWorkspace()
-                .getChildAt(launcher.getWorkspace().getCurrentPage()))
-                .getShortcutsAndWidgets();
+        Workspace workspace = launcher.getWorkspace();
+        CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
+        ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets();
+
+        boolean workspaceClipChildren = workspace.getClipChildren();
+        boolean workspaceClipToPadding = workspace.getClipToPadding();
+        boolean cellLayoutClipChildren = cellLayout.getClipChildren();
+        boolean cellLayoutClipToPadding = cellLayout.getClipToPadding();
+
+        workspace.setClipChildren(false);
+        workspace.setClipToPadding(false);
+        cellLayout.setClipChildren(false);
+        cellLayout.setClipToPadding(false);
 
         // Hotseat and QSB takes up two additional rows.
         int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
@@ -111,6 +124,27 @@
 
         addWorkspaceScrimAnimationForState(launcher, BACKGROUND_APP, 0);
         addWorkspaceScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+
+        AnimatorListener resetClipListener = new AnimatorListenerAdapter() {
+            int numAnimations = mAnimators.size();
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                numAnimations--;
+                if (numAnimations > 0) {
+                    return;
+                }
+
+                workspace.setClipChildren(workspaceClipChildren);
+                workspace.setClipToPadding(workspaceClipToPadding);
+                cellLayout.setClipChildren(cellLayoutClipChildren);
+                cellLayout.setClipToPadding(cellLayoutClipToPadding);
+            }
+        };
+
+        for (Animator a : mAnimators) {
+            a.addListener(resetClipListener);
+        }
     }
 
     /**
@@ -134,10 +168,6 @@
      * @param totalRows Total number of rows.
      */
     private void addStaggeredAnimationForView(View v, int row, int totalRows) {
-        if (v == mViewToIgnore) {
-            return;
-        }
-
         // Invert the rows, because we stagger starting from the bottom of the screen.
         int invertedRow = totalRows - row;
         // Add 1 to the inverted row so that the bottom most row has a start delay.
@@ -149,6 +179,10 @@
         springTransY.setStartDelay(startDelay);
         mAnimators.add(springTransY);
 
+        if (v == mViewToIgnore) {
+            return;
+        }
+
         v.setAlpha(0);
         ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f);
         alpha.setInterpolator(LINEAR);
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 6a9abd5..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
@@ -37,6 +37,7 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -108,7 +109,6 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.LauncherEventUtil;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
@@ -226,7 +226,7 @@
                 return;
             }
 
-            BackgroundExecutor.get().submit(() -> {
+            BACKGROUND_EXECUTOR.execute(() -> {
                 TaskView taskView = getTaskView(taskId);
                 if (taskView == null) {
                     return;
@@ -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) {
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index 5394f49..01dcff2 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -33,5 +33,5 @@
     <string name="time_left_for_app" msgid="3111996412933644358">"Encore <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
     <string name="title_app_suggestions" msgid="4185902664111965088">"Suggestions d\'applications"</string>
     <string name="all_apps_label" msgid="8542784161730910663">"Toutes les applications"</string>
-    <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Vos applications prévues"</string>
+    <string name="all_apps_prediction_tip" msgid="2672336544844936186">"Applications prévues pour vous"</string>
 </resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 0467af4..387d509 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -27,7 +27,7 @@
     <string name="accessibility_close_task" msgid="5354563209433803643">"बंद करें"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सभी ऐप्लिकेशन बंद करें"</string>
-    <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए एेप्लिकेशन"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए ऐप्लिकेशन"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 मिनट"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g> और चलेगा"</string>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 1ca558a..cccece7 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -27,7 +27,7 @@
     <string name="accessibility_close_task" msgid="5354563209433803643">"बंद"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अ‍ॅप वापर सेटिंग्ज"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सर्व साफ करा"</string>
-    <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अॅप्स"</string>
+    <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अ‍ॅप्स"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"१मिहून कमी"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g>शिल्लक आहे"</string>
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 8951363..b59e133 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -10,7 +10,8 @@
 
 public class QuickstepTestInformationHandler extends TestInformationHandler {
 
-    public QuickstepTestInformationHandler(Context context) { }
+    public QuickstepTestInformationHandler(Context context) {
+    }
 
     @Override
     public Bundle call(String method) {
@@ -29,6 +30,12 @@
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
                 return response;
             }
+
+            case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        TouchInteractionService.isInputMonitorInitialized());
+                break;
+            }
         }
 
         return super.call(method);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index f27ba85..7a1d0e8 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep;
 
+import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR;
+
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.Context;
@@ -25,7 +27,6 @@
 import com.android.launcher3.MainThreadExecutor;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.systemui.shared.system.RecentTaskInfoCompat;
 import com.android.systemui.shared.system.TaskDescriptionCompat;
@@ -43,7 +44,6 @@
 
     private final KeyguardManagerCompat mKeyguardManager;
     private final MainThreadExecutor mMainThreadExecutor;
-    private final BackgroundExecutor mBgThreadExecutor;
 
     // The list change id, increments as the task list changes in the system
     private int mChangeId;
@@ -56,7 +56,6 @@
 
     public RecentTasksList(Context context) {
         mMainThreadExecutor = new MainThreadExecutor();
-        mBgThreadExecutor = BackgroundExecutor.get();
         mKeyguardManager = new KeyguardManagerCompat(context);
         mChangeId = 1;
         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
@@ -67,7 +66,7 @@
      */
     public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) {
         // Kick off task loading in the background
-        mBgThreadExecutor.submit(() -> {
+        BACKGROUND_EXECUTOR.execute(() -> {
             ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */);
             mMainThreadExecutor.execute(() -> callback.accept(tasks));
         });
@@ -93,7 +92,7 @@
         }
 
         // Kick off task loading in the background
-        mBgThreadExecutor.submit(() -> {
+        BACKGROUND_EXECUTOR.execute(() -> {
             ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly);
 
             mMainThreadExecutor.execute(() -> {
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index d9fcf4d..d0956d1 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -88,7 +88,7 @@
      */
     @Test
     public void testPredictionExistsInAllApps() {
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         mLauncher.pressHome().switchToAllApps();
 
         // Dispatch an update
@@ -103,7 +103,7 @@
      */
     @Test
     public void testPredictionsDeferredUntilHome() {
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         sendPredictionUpdate(mSampleApp1, mSampleApp2);
         mLauncher.pressHome().switchToAllApps();
         waitForLauncherCondition("Predictions were not updated in loading state",
@@ -120,7 +120,7 @@
 
     @Test
     public void testPredictionsDisabled() {
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         sendPredictionUpdate();
         mLauncher.pressHome().switchToAllApps();
 
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index c0859d3..50f92da 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -88,7 +88,7 @@
     <string name="notification_dots_title" msgid="9062440428204120317">"Punts de notificació"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"Activats"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"Desactivats"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"Cal que tingui accés a les notificacions"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"Cal accés a les notificacions"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Per veure els punts de notificació, activa les notificacions de l\'aplicació <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Canvia la configuració"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"Mostra els punts de notificació"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 3054214..211f735 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -30,10 +30,10 @@
     <string name="home_screen" msgid="806512411299847073">"Layar utama"</string>
     <string name="custom_actions" msgid="3747508247759093328">"Tindakan khusus"</string>
     <string name="long_press_widget_to_add" msgid="7699152356777458215">"Sentuh lama untuk memilih widget."</string>
-    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Tap dua kalip &amp; tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
+    <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Ketuk dua kali &amp; tahan untuk mengambil widget atau menggunakan tindakan khusus."</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"lebar %1$d x tinggi %2$d"</string>
-    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Tap lama untuk menempatkan secara manual"</string>
+    <string name="add_item_request_drag_hint" msgid="5899764264480397019">"Sentuh lama untuk menempatkan secara manual"</string>
     <string name="place_automatically" msgid="8064208734425456485">"Tambahkan otomatis"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"Telusuri aplikasi"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"Memuat aplikasi…"</string>
@@ -41,8 +41,8 @@
     <string name="all_apps_search_market_message" msgid="1366263386197059176">"Telusuri aplikasi lainnya"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikasi"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifikasi"</string>
-    <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Tap lama untuk memilih pintasan."</string>
-    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Tap dua kali &amp; tahan untuk memilih pintasan atau menggunakan tindakan khusus."</string>
+    <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Sentuh lama untuk memilih pintasan."</string>
+    <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Ketuk dua kali &amp; tahan untuk memilih pintasan atau menggunakan tindakan khusus."</string>
     <string name="out_of_space" msgid="4691004494942118364">"Tidak ada ruang lagi pada layar Utama ini."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"Tidak ada ruang tersisa di baki Favorit"</string>
     <string name="all_apps_button_label" msgid="8130441508702294465">"Daftar aplikasi"</string>
@@ -73,8 +73,8 @@
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Layar utama %1$d dari %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Halaman layar utama baru"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
-    <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap untuk menutup folder"</string>
-    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap untuk menyimpan ganti nama"</string>
+    <string name="folder_tap_to_close" msgid="4625795376335528256">"Ketuk untuk menutup folder"</string>
+    <string name="folder_tap_to_rename" msgid="4017685068016979677">"Ketuk untuk menyimpan ganti nama"</string>
     <string name="folder_closed" msgid="4100806530910930934">"Folder ditutup"</string>
     <string name="folder_renamed" msgid="1794088362165669656">"Folder diganti namanya menjadi <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format" msgid="6629239338071103179">"Folder: <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 19c0697..49e3898 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -35,18 +35,18 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d रूंद बाय %2$d उंच"</string>
     <string name="add_item_request_drag_hint" msgid="5899764264480397019">"स्वतः ठेवण्यासाठी स्पर्श करा आणि धरून ठेवा"</string>
     <string name="place_automatically" msgid="8064208734425456485">"आपोआप जोडा"</string>
-    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"अॅप्स शोधा"</string>
-    <string name="all_apps_loading_message" msgid="5813968043155271636">"अॅप्स लोड करत आहे…"</string>
-    <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अॅप्स आढळले नाहीत"</string>
-    <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक अॅप्स शोधा"</string>
+    <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"अ‍ॅप्स शोधा"</string>
+    <string name="all_apps_loading_message" msgid="5813968043155271636">"अ‍ॅप्स लोड करत आहे…"</string>
+    <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अ‍ॅप्स आढळले नाहीत"</string>
+    <string name="all_apps_search_market_message" msgid="1366263386197059176">"अधिक अ‍ॅप्स शोधा"</string>
     <string name="label_application" msgid="8531721983832654978">"ॲप"</string>
     <string name="notifications_header" msgid="1404149926117359025">"सूचना"</string>
     <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"शॉर्टकट निवडण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"शॉर्टकट निवडण्यासाठी किंवा कस्टम क्रिया वापरण्यासाठी दोनदा टॅप करा आणि धरून ठेवा."</string>
     <string name="out_of_space" msgid="4691004494942118364">"या मुख्य स्क्रीनवर आणखी जागा नाही."</string>
     <string name="hotseat_out_of_space" msgid="7448809638125333693">"आवडीच्या ट्रे मध्ये आणखी जागा नाही"</string>
-    <string name="all_apps_button_label" msgid="8130441508702294465">"अॅप्स सूची"</string>
-    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"वैयक्तिक अॅप्स सूची"</string>
+    <string name="all_apps_button_label" msgid="8130441508702294465">"अ‍ॅप्स सूची"</string>
+    <string name="all_apps_button_personal_label" msgid="1315764287305224468">"वैयक्तिक अ‍ॅप्स सूची"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"कामाच्या ठिकाणी वापरली जाणाऱ्या अॅप्सची सूची"</string>
     <string name="all_apps_home_button_label" msgid="252062713717058851">"होम"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"काढा"</string>
@@ -136,7 +136,7 @@
     <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"कामाची अ‍ॅप्स येथे मिळवा"</string>
     <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"प्रत्येक कार्य अ‍ॅपला एक बॅज असतो आणि तो तुमच्या संस्थेकडून सुरक्षित ठेवला जातो. अधिक सहज अ‍ॅक्सेससाठी अ‍ॅप्स तुमच्या होम स्क्रीनवर हलवा."</string>
     <string name="work_mode_on_label" msgid="4781128097185272916">"तुमच्या संस्थेकडून व्यवस्थापित"</string>
-    <string name="work_mode_off_label" msgid="3194894777601421047">"सूचना आणि अॅप्स बंद आहेत"</string>
+    <string name="work_mode_off_label" msgid="3194894777601421047">"सूचना आणि अ‍ॅप्स बंद आहेत"</string>
     <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"बंद करा"</string>
     <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"बंद केले"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"हे करता आले नाही: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 34e267d..23b00d0 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -90,7 +90,7 @@
     <string name="notification_dots_title" msgid="9062440428204120317">"Значки уведомлений"</string>
     <string name="notification_dots_desc_on" msgid="1679848116452218908">"Включены"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"Отключены"</string>
-    <string name="title_missing_notification_access" msgid="7503287056163941064">"Нет доступа к уведомлениям"</string>
+    <string name="title_missing_notification_access" msgid="7503287056163941064">"Нужен доступ к уведомлениям"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"Чтобы показывать значки уведомлений, включите уведомления в приложении \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
     <string name="title_change_settings" msgid="1376365968844349552">"Изменить настройки"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"Показывать значки уведомлений"</string>
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index f69b172..5055309 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -135,10 +135,6 @@
 
     public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item,
             @Nullable String sourceContainer) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "startActivitySafely 1");
-        }
         if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
             Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
             return false;
@@ -162,10 +158,6 @@
                 startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
             } else if (user == null || user.equals(Process.myUserHandle())) {
                 // Could be launching some bookkeeping activity
-                if (TestProtocol.sDebugTracing) {
-                    android.util.Log.d(TestProtocol.NO_START_TAG,
-                            "startActivitySafely 2");
-                }
                 startActivity(intent, optsBundle);
                 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
                         Process.myUserHandle(), sourceContainer);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 1619e36..22c69f5 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -319,9 +319,6 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_START_TAG, "BubbleTextView.onTouchEvent " + event);
-        }
         // Call the superclass onTouchEvent first, because sometimes it changes the state to
         // isPressed() on an ACTION_UP
         boolean result = super.onTouchEvent(event);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index d9af4da..9229832 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -952,10 +952,14 @@
         mHandler.removeCallbacks(mHandleDeferredResume);
         Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
 
-        for (OnResumeCallback cb : mOnResumeCallbacks) {
-            cb.onLauncherResume();
+        if (!mOnResumeCallbacks.isEmpty()) {
+            final ArrayList<OnResumeCallback> resumeCallbacks = new ArrayList<>(mOnResumeCallbacks);
+            mOnResumeCallbacks.clear();
+            for (int i = resumeCallbacks.size() - 1; i >= 0; i--) {
+                resumeCallbacks.get(i).onLauncherResume();
+            }
+            resumeCallbacks.clear();
         }
-        mOnResumeCallbacks.clear();
 
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onResume();
@@ -1805,11 +1809,6 @@
 
     public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
             @Nullable String sourceContainer) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "startActivitySafely outer");
-        }
-
         if (!hasBeenResumed()) {
             // Workaround an issue where the WM launch animation is clobbered when finishing the
             // recents animation into launcher. Defer launching the activity until Launcher is
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 2c8c208..ccd6efa 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -429,11 +429,6 @@
         // Only change the stable states after the transitions have finished
         if (state != mCurrentStableState) {
             mLastStableState = state.getHistoryForState(mCurrentStableState);
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG,
-                        "mCurrentStableState = " + state.getClass().getSimpleName() + " @ " +
-                                android.util.Log.getStackTraceString(new Throwable()));
-            }
             mCurrentStableState = state;
         }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index f8e4c9d..bd52ffe 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -47,6 +47,7 @@
 import android.widget.ScrollView;
 
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.touch.OverScroll;
@@ -367,6 +368,7 @@
      */
     protected void onPageEndTransition() {
         mWasInOverscroll = false;
+        AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
     }
 
     protected int getUnboundedScrollX() {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index ea9b077..4a2109e 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -297,7 +298,11 @@
 
     @Override
     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
-        // This is filled in {@link AllAppsRecyclerView}
+        if (getApps().hasFilter()) {
+            targetParent.containerType = ContainerType.SEARCHRESULT;
+        } else {
+            targetParent.containerType = ContainerType.ALLAPPS;
+        }
     }
 
     @Override
@@ -626,9 +631,6 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_START_TAG, "AllAppsContainerView.dispatchTouchEvent " + ev);
-        }
         final boolean result = super.dispatchTouchEvent(ev);
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 4683893..a64374b 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -168,10 +168,6 @@
     @Override
     public void setStateWithAnimation(LauncherState toState,
             AnimatorSetBuilder builder, AnimationConfig config) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG,
-                    "setStateWithAnimation " + toState.getClass().getSimpleName());
-        }
         float targetProgress = toState.getVerticalProgress(mLauncher);
         if (Float.compare(mProgress, targetProgress) == 0) {
             setAlphas(toState, config, builder);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 2ad92e1..1369441 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -301,11 +301,6 @@
     }
 
     private void refreshRecyclerView() {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "refreshRecyclerView @ " + android.util.Log.getStackTraceString(
-                            new Throwable()));
-        }
         if (mAdapter != null) {
             mAdapter.notifyDataSetChanged();
         }
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index 395fed2..91a3106 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -96,7 +96,10 @@
             }
         });
 
-        mSpring.addUpdateListener((animation, value, velocity) -> mSpringEnded = false);
+        mSpring.addUpdateListener((animation, value, velocity) -> {
+            mSpringEnded = false;
+            mEnded = false;
+        });
         mSpring.addEndListener((animation, canceled, value, velocity) -> {
             mSpringEnded = true;
             tryEnding();
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 43ae651..81c95cb 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -53,9 +53,6 @@
     }
 
     public static void sendStateEventToTest(Context context, int stateOrdinal) {
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "sendStateEventToTest");
-        }
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 54efcb7..45639e0 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -105,7 +105,7 @@
             "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
 
     public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag(
-            "ENABLE_HINTS_IN_OVERVIEW", true,
+            "ENABLE_HINTS_IN_OVERVIEW", false,
             "Show chip hints and gleams on the overview screen");
 
     public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index d2e1961..bab454f 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -74,6 +74,11 @@
                 break;
             }
 
+            case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true);
+                break;
+            }
+
             case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
                 TestProtocol.sDebugTracing = true;
                 break;
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 3774042..e28eba8 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -66,6 +66,7 @@
             "all-apps-to-overview-swipe-height";
     public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
             "home-to-all-apps-swipe-height";
+    public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized";
     public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
     public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
     public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
@@ -73,10 +74,7 @@
     public static boolean sDebugTracing = false;
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
-    public static final String NO_ALLAPPS_EVENT_TAG = "b/133867119";
     public static final String NO_DRAG_TAG = "b/133009122";
-    public static final String NO_START_TAG = "b/132900132";
     public static final String NO_START_TASK_TAG = "b/133765434";
     public static final String NO_OVERVIEW_EVENT_TAG = "b/134532571";
-    public static final String EVENTS_TO_OVERVIEW_MISSING_TAG = "b/133867119";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 6f53140..ae69f3b 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -31,7 +31,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 
@@ -43,7 +42,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -118,9 +116,6 @@
 
     @Override
     public final boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onControllerInterceptTouchEvent 1 " + ev);
-        }
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = !canInterceptTouch(ev);
             if (mNoIntercept) {
@@ -150,9 +145,6 @@
             return false;
         }
 
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onControllerInterceptTouchEvent 2 ");
-        }
         onControllerTouchEvent(ev);
         return mDetector.isDraggingOrSettling();
     }
@@ -240,9 +232,6 @@
 
     @Override
     public void onDragStart(boolean start) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragStart 1 " + start);
-        }
         mStartState = mLauncher.getStateManager().getState();
         if (mStartState == ALL_APPS) {
             mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
@@ -252,9 +241,6 @@
             mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER;
         }
         if (mCurrentAnimation == null) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragStart 2");
-            }
             mFromState = mStartState;
             mToState = null;
             cancelAnimationControllers();
@@ -376,9 +362,6 @@
 
     @Override
     public void onDragEnd(float velocity, boolean fling) {
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onDragEnd");
-        }
         final int logAction = fling ? Touch.FLING : Touch.SWIPE;
 
         boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
@@ -515,9 +498,6 @@
     }
 
     protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onSwipeInteractionCompleted 1");
-        }
         if (mAtomicComponentsController != null) {
             mAtomicComponentsController.getAnimationPlayer().end();
             mAtomicComponentsController = null;
@@ -531,18 +511,17 @@
             shouldGoToTargetState = !reachedTarget;
         }
         if (shouldGoToTargetState) {
-            if (targetState != mStartState) {
-                logReachedState(logAction, targetState);
-            }
-            mLauncher.getStateManager().goToState(targetState, false /* animated */);
-
-            if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-                android.util.Log.e(
-                        TestProtocol.NO_ALLAPPS_EVENT_TAG, "onSwipeInteractionCompleted 2");
-            }
+            goToTargetState(targetState, logAction);
         }
     }
 
+    protected void goToTargetState(LauncherState targetState, int logAction) {
+        if (targetState != mStartState) {
+            logReachedState(logAction, targetState);
+        }
+        mLauncher.getStateManager().goToState(targetState, false /* animated */);
+    }
+
     private void logReachedState(int logAction, LauncherState targetState) {
         // Transition complete. log the action
         mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
@@ -563,9 +542,6 @@
     }
 
     private void cancelAnimationControllers() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "cancelAnimationControllers");
-        }
         mCurrentAnimation = null;
         cancelAtomicComponentsController();
         mDetector.finishedScrolling();
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 85f763d..2895a89 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -68,28 +68,12 @@
     }
 
     private static void onClick(View v, String sourceContainer) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "onClick 1");
-        }
         // Make sure that rogue clicks don't get through while allapps is launching, or after the
         // view has detached (it's possible for this to happen if the view is removed mid touch).
-        if (v.getWindowToken() == null) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_START_TAG,
-                        "onClick 2");
-            }
-            return;
-        }
+        if (v.getWindowToken() == null) return;
 
         Launcher launcher = Launcher.getLauncher(v.getContext());
-        if (!launcher.getWorkspace().isFinishedSwitchingState()) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_START_TAG,
-                        "onClick 3");
-            }
-            return;
-        }
+        if (!launcher.getWorkspace().isFinishedSwitchingState()) return;
 
         Object tag = v.getTag();
         if (tag instanceof WorkspaceItemInfo) {
@@ -99,10 +83,6 @@
                 onClickFolderIcon(v);
             }
         } else if (tag instanceof AppInfo) {
-            if (TestProtocol.sDebugTracing) {
-                android.util.Log.d(TestProtocol.NO_START_TAG,
-                        "onClick 4");
-            }
             startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher,
                     sourceContainer == null ? CONTAINER_ALL_APPS: sourceContainer);
         } else if (tag instanceof LauncherAppWidgetInfo) {
@@ -234,10 +214,6 @@
 
     private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
             @Nullable String sourceContainer) {
-        if (TestProtocol.sDebugTracing) {
-            android.util.Log.d(TestProtocol.NO_START_TAG,
-                    "startAppShortcutOrInfoActivity");
-        }
         Intent intent;
         if (item instanceof PromiseAppInfo) {
             PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java
index 3d45404..3777a41 100644
--- a/src/com/android/launcher3/touch/SwipeDetector.java
+++ b/src/com/android/launcher3/touch/SwipeDetector.java
@@ -158,9 +158,6 @@
     // SETTLING -> (View settled) -> IDLE
 
     private void setState(ScrollState newState) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "setState -- start: " + newState);
-        }
         if (DBG) {
             Log.d(TAG, "setState:" + mState + "->" + newState);
         }
@@ -168,9 +165,6 @@
         if (newState == ScrollState.DRAGGING) {
             initializeDragging();
             if (mState == ScrollState.IDLE) {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "setState -- 1: " + newState);
-                }
                 reportDragStart(false /* recatch */);
             } else if (mState == ScrollState.SETTLING) {
                 reportDragStart(true /* recatch */);
@@ -181,11 +175,6 @@
         }
 
         mState = newState;
-        if (com.android.launcher3.testing.TestProtocol.sDebugTracing) {
-            android.util.Log.e(TestProtocol.NO_ALLAPPS_EVENT_TAG,
-                    "setState: " + newState + " @ " + android.util.Log.getStackTraceString(
-                            new Throwable()));
-        }
     }
 
     public boolean isDraggingOrSettling() {
@@ -324,15 +313,9 @@
                     break;
                 }
                 mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos, mIsRtl);
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onTouchEvent 1");
-                }
 
                 // handle state and listener calls.
                 if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) {
-                    if (TestProtocol.sDebugTracing) {
-                        Log.d(TestProtocol.NO_ALLAPPS_EVENT_TAG, "onTouchEvent 2");
-                    }
                     setState(ScrollState.DRAGGING);
                 }
                 if (mState == ScrollState.DRAGGING) {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 1e70c7f..8bf33bf 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -152,9 +152,6 @@
     }
 
     private TouchController findControllerToHandleTouch(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "findControllerToHandleTouch " + ev);
-        }
         if (shouldDisableGestures(ev)) return null;
 
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
@@ -259,9 +256,6 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.NO_START_TAG, "BaseDragLayer.dispatchTouchEvent " + ev);
-        }
         switch (ev.getAction()) {
             case ACTION_DOWN: {
                 float x = ev.getX();
@@ -315,9 +309,6 @@
      * Proxies the touch events to the gesture handlers
      */
     public boolean proxyTouchEvent(MotionEvent ev) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.EVENTS_TO_OVERVIEW_MISSING_TAG, "proxyTouchEvent " + ev);
-        }
         boolean handled;
         if (mProxyTouchController != null) {
             handled = mProxyTouchController.onControllerTouchEvent(ev);
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index ab4b576..4fdd83b 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -656,8 +656,7 @@
         canvas.restoreToCount(count);
     }
 
-    public void onListenerViewClosed() {
-        // Fast finish here.
+    public void fastFinish() {
         if (mEndRunnable != null) {
             mEndRunnable.run();
             mEndRunnable = null;
@@ -757,7 +756,7 @@
         view.setVisibility(INVISIBLE);
         parent.addView(view);
         dragLayer.addView(view.mListenerView);
-        view.mListenerView.setListener(view::onListenerViewClosed);
+        view.mListenerView.setListener(view::fastFinish);
 
         view.mEndRunnable = () -> {
             view.mEndRunnable = null;
diff --git a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
index fa23b8d..6a6916e 100644
--- a/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
+++ b/tests/src/com/android/launcher3/testcomponent/TestCommandReceiver.java
@@ -32,13 +32,14 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Base64;
 
+import androidx.test.InstrumentationRegistry;
+
+import com.android.launcher3.tapl.TestHelpers;
+
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 
-import androidx.test.InstrumentationRegistry;
-
 /**
  * Content provider to receive commands from tests
  */
@@ -47,6 +48,7 @@
     public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher";
     public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher";
     public static final String KILL_PROCESS = "kill-process";
+    public static final String GET_SYSTEM_HEALTH_MESSAGE = "get-system-health-message";
 
     @Override
     public boolean onCreate() {
@@ -99,6 +101,12 @@
                         killBackgroundProcesses(arg);
                 return null;
             }
+
+            case GET_SYSTEM_HEALTH_MESSAGE: {
+                final Bundle response = new Bundle();
+                response.putString("result", TestHelpers.getSystemHealthMessage(getContext()));
+                return response;
+            }
         }
         return super.call(method, arg, extras);
     }
@@ -122,7 +130,8 @@
             // Create an empty file so that we can pass its descriptor
             try {
                 file.createNewFile();
-            } catch (IOException e) { }
+            } catch (IOException e) {
+            }
         }
 
         return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index e39fc76..361f2fb 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -51,6 +51,7 @@
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.tapl.TestHelpers;
+import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.FailureWatcher;
@@ -98,7 +99,11 @@
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
-        if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
+        if (TestHelpers.isInLauncherProcess()) {
+            Utilities.enableRunningInTestHarnessForTests();
+            mLauncher.setSystemHealthSupplier(() -> TestCommandReceiver.callCommand(
+                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE).getString("result"));
+        }
     }
 
     protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index 58c74ce..a76b4a4 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -72,7 +72,7 @@
         writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
 
         // Launch the home activity
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         waitForModelLoaded();
 
         mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString());
@@ -88,7 +88,7 @@
                         info.getComponent().getClassName(), 2, 2));
 
         // Launch the home activity
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         waitForModelLoaded();
 
         // Verify widget present
@@ -105,7 +105,7 @@
                 .build());
 
         // Launch the home activity
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         waitForModelLoaded();
 
         mLauncher.getWorkspace().getHotseatFolder("Folder: Copy");
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index c93c20a..d9edc35 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -53,8 +53,8 @@
 
     @Test
     public void workTabExists() {
-        mActivityMonitor.startLauncher();
-
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
 
         /*
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index dc72bda..3206a69 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -98,7 +98,7 @@
         lockRotation(true);
 
         clearHomescreen();
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
 
         final Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
 
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 4529a80..276c614 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -55,7 +55,7 @@
 
     private void performTest() throws Throwable {
         clearHomescreen();
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
 
         final LauncherAppWidgetProviderInfo widgetInfo =
                 TestViewHelpers.findWidgetProvider(this, false /* hasConfigureScreen */);
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index d36126b..3a7df64 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -267,7 +267,7 @@
         resetLoaderState();
 
         // Launch the home activity
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
         waitForModelLoaded();
     }
 
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 6122dae..a9a5090 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -131,7 +131,7 @@
         lockRotation(true);
 
         clearHomescreen();
-        mActivityMonitor.startLauncher();
+        mDevice.pressHome();
 
         // Open Pin item activity
         BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
index 2aba7a5..2042403 100644
--- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java
@@ -15,11 +15,6 @@
  */
 package com.android.launcher3.util.rule;
 
-import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
-
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
 import android.app.Activity;
 import android.app.Application;
 import android.app.Application.ActivityLifecycleCallbacks;
@@ -52,26 +47,15 @@
     }
 
     public Callable<Boolean> itemExists(final ItemOperator op) {
-        return new Callable<Boolean>() {
-
-            @Override
-            public Boolean call() throws Exception {
-                Launcher launcher = getActivity();
-                if (launcher == null) {
-                    return false;
-                }
-                return launcher.getWorkspace().getFirstMatch(op) != null;
+        return () -> {
+            Launcher launcher = getActivity();
+            if (launcher == null) {
+                return false;
             }
+            return launcher.getWorkspace().getFirstMatch(op) != null;
         };
     }
 
-    /**
-     * Starts the launcher activity in the target package.
-     */
-    public void startLauncher() {
-        getInstrumentation().startActivitySync(getHomeIntentInPackage(getTargetContext()));
-    }
-
     private class MyStatement extends Statement implements ActivityLifecycleCallbacks {
 
         private final Statement mBase;
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index ace49e9..ae93867 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.tapl;
 
+import android.graphics.Rect;
+
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.Direction;
@@ -49,9 +51,8 @@
                      mLauncher.addContextLayer("want to fling forward in overview")) {
             LauncherInstrumentation.log("Overview.flingForward before fling");
             final UiObject2 overview = verifyActiveContainer();
-            overview.setGestureMargins(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0);
-            overview.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-            mLauncher.waitForIdle();
+            mLauncher.scroll(overview, Direction.LEFT, 1,
+                    new Rect(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0), 20);
             verifyActiveContainer();
         }
     }
@@ -86,9 +87,8 @@
                      mLauncher.addContextLayer("want to fling backward in overview")) {
             LauncherInstrumentation.log("Overview.flingBackward before fling");
             final UiObject2 overview = verifyActiveContainer();
-            overview.setGestureMargins(0, 0, mLauncher.getEdgeSensitivityWidth(), 0);
-            overview.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity()));
-            mLauncher.waitForIdle();
+            mLauncher.scroll(overview, Direction.RIGHT, 1,
+                    new Rect(0, 0, mLauncher.getEdgeSensitivityWidth(), 0), 20);
             verifyActiveContainer();
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 11b0665..0fed337 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -39,7 +39,6 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.DropBoxManager;
 import android.os.Parcelable;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -73,6 +72,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
+import java.util.function.Supplier;
 
 /**
  * The main tapl object. The only object that can be explicitly constructed by the using code. It
@@ -133,6 +133,7 @@
     private int mExpectedRotation = Surface.ROTATION_0;
     private final Uri mTestProviderUri;
     private final Deque<String> mDiagnosticContext = new LinkedList<>();
+    private Supplier<String> mSystemHealthSupplier;
 
     /**
      * Constructs the root of TAPL hierarchy. You get all other objects from it.
@@ -285,79 +286,24 @@
         return "Background";
     }
 
-    private static String truncateCrash(String text, int maxLines) {
-        String[] lines = text.split("\\r?\\n");
-        StringBuilder ret = new StringBuilder();
-        for (int i = 0; i < maxLines && i < lines.length; i++) {
-            ret.append(lines[i]);
-            ret.append('\n');
-        }
-        if (lines.length > maxLines) {
-            ret.append("... ");
-            ret.append(lines.length - maxLines);
-            ret.append(" more lines truncated ...\n");
-        }
-        return ret.toString();
-    }
-
-    private String checkCrash(String label) {
-        DropBoxManager dropbox = (DropBoxManager) getContext().getSystemService(
-                Context.DROPBOX_SERVICE);
-        Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
-
-        long timestamp = 0;
-        DropBoxManager.Entry entry;
-        int crashCount = 0;
-        StringBuilder errorDetails = new StringBuilder();
-        while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
-            String dropboxSnippet;
-            try {
-                dropboxSnippet = entry.getText(4096);
-            } finally {
-                entry.close();
-            }
-
-            crashCount++;
-            errorDetails.append(label);
-            errorDetails.append(": ");
-            errorDetails.append(truncateCrash(dropboxSnippet, 40));
-            errorDetails.append("    ...\n");
-
-            timestamp = entry.getTimeMillis();
-        }
-        Assert.assertEquals(errorDetails.toString(), 0, crashCount);
-        return crashCount > 0 ? errorDetails.toString() : null;
+    public void setSystemHealthSupplier(Supplier<String> supplier) {
+        this.mSystemHealthSupplier = supplier;
     }
 
     private String getSystemHealthMessage() {
+        final String testPackage = getContext().getPackageName();
         try {
-            StringBuilder errors = new StringBuilder();
-
-            final String testPackage = getContext().getPackageName();
-            try {
-                mDevice.executeShellCommand("pm grant " + testPackage +
-                        " android.permission.READ_LOGS");
-                mDevice.executeShellCommand("pm grant " + testPackage +
-                        " android.permission.PACKAGE_USAGE_STATS");
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-
-            final String[] labels = {
-                    "system_server_crash",
-                    "system_server_native_crash",
-                    "system_server_anr",
-            };
-
-            for (String label : labels) {
-                final String crash = checkCrash(label);
-                if (crash != null) errors.append(crash);
-            }
-
-            return errors.length() != 0 ? errors.toString() : null;
-        } catch (Exception e) {
-            return null;
+            mDevice.executeShellCommand("pm grant " + testPackage +
+                    " android.permission.READ_LOGS");
+            mDevice.executeShellCommand("pm grant " + testPackage +
+                    " android.permission.PACKAGE_USAGE_STATS");
+        } catch (IOException e) {
+            e.printStackTrace();
         }
+
+        return mSystemHealthSupplier != null
+                ? mSystemHealthSupplier.get()
+                : TestHelpers.getSystemHealthMessage(getContext());
     }
 
     private void fail(String message) {
@@ -444,6 +390,8 @@
     }
 
     private UiObject2 verifyContainerType(ContainerType containerType) {
+        waitForTouchInteractionService();
+
         assertEquals("Unexpected display rotation",
                 mExpectedRotation, mDevice.getDisplayRotation());
 
@@ -514,6 +462,18 @@
         }
     }
 
+    private void waitForTouchInteractionService() {
+        for (int i = 0; i < 100; ++i) {
+            if (getTestInfo(
+                    TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED).
+                    getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD)) {
+                return;
+            }
+            SystemClock.sleep(100);
+        }
+        fail("TouchInteractionService didn't connect");
+    }
+
     Parcelable executeAndWaitForEvent(Runnable command,
             UiAutomation.AccessibilityEventFilter eventFilter, String message) {
         try {
@@ -820,6 +780,22 @@
                 endY = (int) (vertCenter - halfGestureHeight);
             }
             break;
+            case LEFT: {
+                startY = endY = rect.centerY();
+                final int horizCenter = rect.centerX();
+                final float halfGestureWidth = rect.width() * percent / 2.0f;
+                startX = (int) (horizCenter - halfGestureWidth);
+                endX = (int) (horizCenter + halfGestureWidth);
+            }
+            break;
+            case RIGHT: {
+                startY = endY = rect.centerY();
+                final int horizCenter = rect.centerX();
+                final float halfGestureWidth = rect.width() * percent / 2.0f;
+                startX = (int) (horizCenter + halfGestureWidth);
+                endX = (int) (horizCenter - halfGestureWidth);
+            }
+            break;
             default:
                 fail("Unsupported direction");
                 return;
diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
index 93554d2..e19f91a 100644
--- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
+++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java
@@ -26,6 +26,9 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.os.DropBoxManager;
+
+import org.junit.Assert;
 
 import java.util.List;
 
@@ -81,4 +84,69 @@
         }
         return "com.android.systemui";
     }
+
+    private static String truncateCrash(String text, int maxLines) {
+        String[] lines = text.split("\\r?\\n");
+        StringBuilder ret = new StringBuilder();
+        for (int i = 0; i < maxLines && i < lines.length; i++) {
+            ret.append(lines[i]);
+            ret.append('\n');
+        }
+        if (lines.length > maxLines) {
+            ret.append("... ");
+            ret.append(lines.length - maxLines);
+            ret.append(" more lines truncated ...\n");
+        }
+        return ret.toString();
+    }
+
+    private static String checkCrash(Context context, String label) {
+        DropBoxManager dropbox = (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
+        Assert.assertNotNull("Unable access the DropBoxManager service", dropbox);
+
+        long timestamp = 0;
+        DropBoxManager.Entry entry;
+        int crashCount = 0;
+        StringBuilder errorDetails = new StringBuilder();
+        while (null != (entry = dropbox.getNextEntry(label, timestamp))) {
+            String dropboxSnippet;
+            try {
+                dropboxSnippet = entry.getText(4096);
+            } finally {
+                entry.close();
+            }
+
+            crashCount++;
+            errorDetails.append(label);
+            errorDetails.append(": ");
+            errorDetails.append(truncateCrash(dropboxSnippet, 40));
+            errorDetails.append("    ...\n");
+
+            timestamp = entry.getTimeMillis();
+        }
+        Assert.assertEquals(errorDetails.toString(), 0, crashCount);
+        return crashCount > 0 ? errorDetails.toString() : null;
+    }
+
+    public static String getSystemHealthMessage(Context context) {
+        try {
+            StringBuilder errors = new StringBuilder();
+
+            final String[] labels = {
+                    "system_server_crash",
+                    "system_server_native_crash",
+                    "system_server_anr",
+            };
+
+            for (String label : labels) {
+                final String crash = checkCrash(context, label);
+                if (crash != null) errors.append(crash);
+            }
+
+            return errors.length() != 0 ? errors.toString() : null;
+        } catch (Exception e) {
+            return "Failed to get system health diags, maybe build your test via .bp instead of "
+                    + ".mk? " + android.util.Log.getStackTraceString(e);
+        }
+    }
 }