Merge "Ensure ArrowPopup alpha is set to 1 at the end of the opening animation." into ub-launcher3-master
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index af1b353..76abe8d 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -263,7 +263,9 @@
      */
     public Bitmap createIconBitmap(Drawable icon, float scale, int size) {
         Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
-
+        if (icon == null) {
+            return bitmap;
+        }
         mCanvas.setBitmap(bitmap);
         mOldBounds.set(icon.getBounds());
 
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index 2966cb1..d84633d 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -505,7 +505,7 @@
     }
 
     static final class IconDB extends SQLiteCacheHelper {
-        private final static int RELEASE_VERSION = 25;
+        private final static int RELEASE_VERSION = 26;
 
         public final static String TABLE_NAME = "icons";
         public final static String COLUMN_ROWID = "rowid";
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index 9ccd477..16d2b67 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
index b37c2e0..a41362f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/FlingAndHoldTouchController.java
@@ -91,5 +91,6 @@
         } else {
             super.onDragEnd(velocity, fling);
         }
+        mMotionPauseDetector.clear();
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
index 51e9495..027fd91 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsUiFactory.java
@@ -120,13 +120,11 @@
      */
     public static void onLauncherStateOrResumeChanged(Launcher launcher) {
         LauncherState state = launcher.getStateManager().getState();
-        if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
-            DeviceProfile profile = launcher.getDeviceProfile();
-            boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
-                    && !profile.isVerticalBarLayout();
-            UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
-                    visible ? 1 : 0, profile.hotseatBarSizePx);
-        }
+        DeviceProfile profile = launcher.getDeviceProfile();
+        boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
+                && !profile.isVerticalBarLayout();
+        UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
+                visible ? 1 : 0, profile.hotseatBarSizePx);
 
         if (state == NORMAL) {
             launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index af25355..a3c942e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -39,6 +39,8 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.Region;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -411,6 +413,11 @@
     }
 
     @Override
+    public boolean deferStartingActivity(Region activeNavBarRegion, MotionEvent ev) {
+        return activeNavBarRegion.contains((int) ev.getX(), (int) ev.getY());
+    }
+
+    @Override
     public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
         return homeBounds;
     }
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 0bdb578..fd60cb8 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -22,8 +22,10 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.Region;
 import android.os.Build;
 import android.os.Handler;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.Interpolator;
 
@@ -103,6 +105,10 @@
      */
     boolean deferStartingActivity(int downHitTarget);
 
+    default boolean deferStartingActivity(Region activeNavBarRegion, MotionEvent ev) {
+        return true;
+    }
+
     boolean supportsLongSwipe(T activity);
 
     AlphaProperty getAlphaProperty(T activity);
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index 3664c97..3e2e9a6 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -52,8 +52,6 @@
             ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
     private static final int ACTION_SHOW_OVERVIEW_FROM_ALT_TAB =
             ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
-    private static final int ACTION_QUICK_STEP =
-            ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
 
     private final InputEventDispatcher mDispatcher;
     private final InputEventReceiver mReceiver;
@@ -98,9 +96,6 @@
                     mConsumer.onShowOverviewFromAltTab();
                     mConsumer.onQuickScrubStart();
                     break;
-                case ACTION_QUICK_STEP:
-                    mConsumer.onQuickStep(event);
-                    break;
                 default:
                     Log.e(TAG, "Invalid virtual event: " + event.getAction());
             }
@@ -139,11 +134,6 @@
         queueVirtualAction(ACTION_QUICK_SCRUB_END, 0);
     }
 
-    public void onQuickStep(MotionEvent event) {
-        event.setAction(ACTION_QUICK_STEP);
-        queue(event);
-    }
-
     public void onNewGesture(@HitTarget int downHitTarget) {
         queueVirtualAction(ACTION_NEW_GESTURE, downHitTarget);
     }
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 84f16cb..63349ed 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -55,9 +55,10 @@
 import com.android.systemui.shared.system.BackgroundExecutor;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.NavigationBarCompat;
-import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import java.util.function.Consumer;
+
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
@@ -80,27 +81,37 @@
     private final InputConsumerController mInputConsumer;
     private final SwipeSharedState mSwipeSharedState;
 
-    private final MotionEventQueue mEventQueue;
+    private final int mDisplayRotation;
+    private final Rect mStableInsets = new Rect();
+
+    private final Consumer<OtherActivityTouchConsumer> mOnCompleteCallback;
     private final MotionPauseDetector mMotionPauseDetector;
     private VelocityTracker mVelocityTracker;
 
+    private WindowTransformSwipeHandler mInteractionHandler;
+
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
     private int mActivePointerId = INVALID_POINTER_ID;
-    private boolean mPassedInitialSlop;
-    // Used for non-deferred gestures to determine when to start dragging
-    private int mQuickStepDragSlop;
+
+    private final float mDragSlop;
+    private final float mTouchSlop;
+
+    // Slop used to check when we start moving window.
+    private boolean mPassedDragSlop;
+    // Slop used to determine when we say that the gesture has started.
+    private boolean mPassedTouchSlop;
+
+    // TODO: Start displacement should have both x and y
     private float mStartDisplacement;
-    private WindowTransformSwipeHandler mInteractionHandler;
-    private int mDisplayRotation;
-    private Rect mStableInsets = new Rect();
 
     public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
             RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
-            @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
+            boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
             TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
-            TouchInteractionLog touchInteractionLog, MotionEventQueue eventQueue,
+            TouchInteractionLog touchInteractionLog,
+            Consumer<OtherActivityTouchConsumer> onCompleteCallback,
             SwipeSharedState swipeSharedState) {
         super(base);
 
@@ -109,17 +120,26 @@
         mHomeIntent = homeIntent;
 
         mMotionPauseDetector = new MotionPauseDetector(base);
-        mEventQueue = eventQueue;
+        mOnCompleteCallback = onCompleteCallback;
         mVelocityTracker = VelocityTracker.obtain();
 
         mActivityControlHelper = activityControl;
-        mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget);
+        mIsDeferredDownTarget = isDeferredDownTarget;
         mOverviewCallbacks = overviewCallbacks;
         mTaskOverlayFactory = taskOverlayFactory;
         mTouchInteractionLog = touchInteractionLog;
         mTouchInteractionLog.setTouchConsumer(this);
         mInputConsumer = inputConsumer;
         mSwipeSharedState = swipeSharedState;
+
+        Display display = getSystemService(WindowManager.class).getDefaultDisplay();
+        mDisplayRotation = display.getRotation();
+        WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+
+        mDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
+        mTouchSlop = NavigationBarCompat.getQuickStepTouchSlopPx();
+        // If active listener isn't null, we are continuing the previous gesture.
+        mPassedTouchSlop = mPassedDragSlop = mSwipeSharedState.getActiveListener() != null;
     }
 
     @Override
@@ -146,9 +166,6 @@
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
-                // If active listener isn't null, we are continuing the previous gesture.
-                mPassedInitialSlop = mSwipeSharedState.getActiveListener() != null;
-                mQuickStepDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
 
                 // 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
@@ -156,9 +173,6 @@
                     startTouchTrackingForWindowAnimation(ev.getEventTime());
                 }
 
-                Display display = getSystemService(WindowManager.class).getDefaultDisplay();
-                mDisplayRotation = display.getRotation();
-                WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
                 RaceConditionTracker.onEvent(DOWN_EVT, EXIT);
                 break;
             }
@@ -182,18 +196,38 @@
                 }
                 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
                 float displacement = getDisplacement(ev);
-                if (!mPassedInitialSlop) {
+
+                if (!mPassedDragSlop) {
                     if (!mIsDeferredDownTarget) {
                         // Normal gesture, ensure we pass the drag slop before we start tracking
                         // the gesture
-                        if (Math.abs(displacement) > mQuickStepDragSlop) {
-                            mPassedInitialSlop = true;
+                        if (Math.abs(displacement) > mDragSlop) {
+                            mPassedDragSlop = true;
                             mStartDisplacement = displacement;
                         }
                     }
                 }
 
-                if (mPassedInitialSlop && mInteractionHandler != null) {
+                if (!mPassedTouchSlop) {
+                    if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) >=
+                            mTouchSlop) {
+                        mPassedTouchSlop = true;
+
+                        mTouchInteractionLog.startQuickStep();
+                        if (mIsDeferredDownTarget) {
+                            // Deferred gesture, start the animation and gesture tracking once
+                            // we pass the actual touch slop
+                            startTouchTrackingForWindowAnimation(ev.getEventTime());
+                        }
+                        if (!mPassedDragSlop) {
+                            mPassedDragSlop = true;
+                            mStartDisplacement = displacement;
+                        }
+                        notifyGestureStarted();
+                    }
+                }
+
+                if (mPassedDragSlop && mInteractionHandler != null) {
                     // Move
                     dispatchMotion(ev, displacement - mStartDisplacement, null);
 
@@ -298,7 +332,7 @@
      * the animation can still be running.
      */
     private void finishTouchTracking(MotionEvent ev) {
-        if (mPassedInitialSlop && mInteractionHandler != null) {
+        if (mPassedDragSlop && mInteractionHandler != null) {
 
             mVelocityTracker.computeCurrentVelocity(1000,
                     ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
@@ -323,6 +357,7 @@
         }
         mVelocityTracker.recycle();
         mVelocityTracker = null;
+        mMotionPauseDetector.clear();
     }
 
     @Override
@@ -348,7 +383,7 @@
         Preconditions.assertUIThread();
         removeListener();
         mInteractionHandler = null;
-        mEventQueue.onConsumerInactive(this);
+        mOnCompleteCallback.accept(this);
     }
 
     private void removeListener() {
@@ -360,11 +395,11 @@
 
     @Override
     public void onQuickScrubStart() {
-        if (!mPassedInitialSlop && mIsDeferredDownTarget && mInteractionHandler == null) {
+        if (!mPassedDragSlop && mIsDeferredDownTarget && mInteractionHandler == null) {
             // If we deferred starting the window animation on touch down, then
             // start tracking now
             startTouchTrackingForWindowAnimation(SystemClock.uptimeMillis());
-            mPassedInitialSlop = true;
+            mPassedDragSlop = true;
         }
 
         mTouchInteractionLog.startQuickScrub();
@@ -390,21 +425,6 @@
         }
     }
 
-    @Override
-    public void onQuickStep(MotionEvent ev) {
-        mTouchInteractionLog.startQuickStep();
-        if (mIsDeferredDownTarget) {
-            // Deferred gesture, start the animation and gesture tracking once we pass the actual
-            // touch slop
-            startTouchTrackingForWindowAnimation(ev.getEventTime());
-        }
-        if (!mPassedInitialSlop) {
-            mPassedInitialSlop = true;
-            mStartDisplacement = getDisplacement(ev);
-        }
-        notifyGestureStarted();
-    }
-
     private float getDisplacement(MotionEvent ev) {
         float eventX = ev.getX();
         float eventY = ev.getY();
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 27f1399..411e593 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -64,8 +64,6 @@
     private final Handler mUiHandler;
     private final Handler mBgHandler;
 
-    private boolean mSwipeGestureInitializing = false;
-
     // These are updated on the background thread
     private ISystemUiProxy mISystemUiProxy;
     private boolean mSwipeUpEnabled = true;
@@ -154,7 +152,7 @@
             return;
         }
 
-        int flags = 0;
+        int flags = FLAG_DISABLE_QUICK_SCRUB;
         if (!mSwipeUpEnabled) {
             flags = FLAG_DISABLE_SWIPE_UP | FLAG_DISABLE_QUICK_SCRUB | FLAG_SHOW_OVERVIEW_BUTTON;
         }
@@ -177,15 +175,6 @@
         }
     }
 
-    @WorkerThread
-    public void setSwipeGestureInitializing(boolean swipeGestureInitializing) {
-        mSwipeGestureInitializing = swipeGestureInitializing;
-    }
-
-    public boolean swipeGestureInitializing() {
-        return mSwipeGestureInitializing;
-    }
-
     public void notifySwipeUpSettingChanged(boolean swipeUpEnabled) {
         mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
         mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, swipeUpEnabled ? 1 : 0, 0).
diff --git a/quickstep/src/com/android/quickstep/OverviewTouchConsumer.java b/quickstep/src/com/android/quickstep/OverviewTouchConsumer.java
new file mode 100644
index 0000000..2638f23
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverviewTouchConsumer.java
@@ -0,0 +1,247 @@
+/*
+ * 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.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_INDEX_SHIFT;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+/**
+ * Touch consumer for handling touch on the recents/Launcher activity.
+ */
+public class OverviewTouchConsumer<T extends BaseDraggingActivity>
+        implements TouchConsumer {
+
+    private static final String TAG = "OverviewTouchConsumer";
+
+    private final ActivityControlHelper<T> mActivityHelper;
+    private final T mActivity;
+    private final BaseDragLayer mTarget;
+    private final int[] mLocationOnScreen = new int[2];
+    private final PointF mDownPos = new PointF();
+    private final int mTouchSlop;
+    private final QuickScrubController mQuickScrubController;
+    private final TouchInteractionLog mTouchInteractionLog;
+
+    private final boolean mStartingInActivityBounds;
+
+    private boolean mTrackingStarted = false;
+    private boolean mInvalidated = false;
+
+    private float mLastProgress = 0;
+    private boolean mStartPending = false;
+    private boolean mEndPending = false;
+    private boolean mWaitForWindowAvailable;
+
+    OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity,
+            boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog,
+            boolean waitForWindowAvailable) {
+        mActivityHelper = activityHelper;
+        mActivity = activity;
+        mTarget = activity.getDragLayer();
+        mTouchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop();
+        mStartingInActivityBounds = startingInActivityBounds;
+
+        mQuickScrubController = mActivity.<RecentsView>getOverviewPanel()
+                .getQuickScrubController();
+        mTouchInteractionLog = touchInteractionLog;
+        mTouchInteractionLog.setTouchConsumer(this);
+
+        mWaitForWindowAvailable = waitForWindowAvailable;
+    }
+
+    @Override
+    public void accept(MotionEvent ev) {
+        if (mInvalidated) {
+            return;
+        }
+        mTouchInteractionLog.addMotionEvent(ev);
+        int action = ev.getActionMasked();
+        if (action == ACTION_DOWN) {
+            if (mStartingInActivityBounds) {
+                startTouchTracking(ev, false /* updateLocationOffset */,
+                        false /* closeActiveWindows */);
+                return;
+            }
+            mTrackingStarted = false;
+            mDownPos.set(ev.getX(), ev.getY());
+        } else if (!mTrackingStarted) {
+            switch (action) {
+                case ACTION_CANCEL:
+                case ACTION_UP:
+                    startTouchTracking(ev, true /* updateLocationOffset */,
+                            false /* closeActiveWindows */);
+                    break;
+                case ACTION_MOVE: {
+                    float displacement = mActivity.getDeviceProfile().isLandscape ?
+                            ev.getX() - mDownPos.x : ev.getY() - mDownPos.y;
+                    if (Math.abs(displacement) >= mTouchSlop) {
+                        // Start tracking only when mTouchSlop is crossed.
+                        startTouchTracking(ev, true /* updateLocationOffset */,
+                                true /* closeActiveWindows */);
+                    }
+                }
+            }
+        }
+
+        if (mTrackingStarted) {
+            sendEvent(ev);
+        }
+
+        if (action == ACTION_UP || action == ACTION_CANCEL) {
+            mInvalidated = true;
+        }
+    }
+
+    private void startTouchTracking(MotionEvent ev, boolean updateLocationOffset,
+            boolean closeActiveWindows) {
+        if (updateLocationOffset) {
+            mTarget.getLocationOnScreen(mLocationOnScreen);
+        }
+
+        // Send down touch event
+        MotionEvent down = MotionEvent.obtainNoHistory(ev);
+        down.setAction(ACTION_DOWN);
+        sendEvent(down);
+
+        mTrackingStarted = true;
+        // Send pointer down for remaining pointers.
+        int pointerCount = ev.getPointerCount();
+        for (int i = 1; i < pointerCount; i++) {
+            down.setAction(ACTION_POINTER_DOWN | (i << ACTION_POINTER_INDEX_SHIFT));
+            sendEvent(down);
+        }
+
+        down.recycle();
+
+        if (closeActiveWindows) {
+            OverviewCallbacks.get(mActivity).closeAllWindows();
+            ActivityManagerWrapper.getInstance()
+                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+            mTouchInteractionLog.startQuickStep();
+        }
+    }
+
+    private void sendEvent(MotionEvent ev) {
+        if (!mTarget.verifyTouchDispatch(this, ev)) {
+            mInvalidated = true;
+            return;
+        }
+        int flags = ev.getEdgeFlags();
+        ev.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
+        ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
+        if (!mTrackingStarted) {
+            mTarget.onInterceptTouchEvent(ev);
+        }
+        mTarget.onTouchEvent(ev);
+        ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
+        ev.setEdgeFlags(flags);
+    }
+
+    @Override
+    public void onQuickScrubStart() {
+        if (mInvalidated) {
+            return;
+        }
+        mTouchInteractionLog.startQuickScrub();
+        if (!mQuickScrubController.prepareQuickScrub(TAG)) {
+            mInvalidated = true;
+            mTouchInteractionLog.endQuickScrub("onQuickScrubStart");
+            return;
+        }
+        OverviewCallbacks.get(mActivity).closeAllWindows();
+        ActivityManagerWrapper.getInstance()
+                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+
+        mStartPending = true;
+        Runnable action = () -> {
+            if (!mQuickScrubController.prepareQuickScrub(TAG)) {
+                mInvalidated = true;
+                mTouchInteractionLog.endQuickScrub("onQuickScrubStart");
+                return;
+            }
+            mActivityHelper.onQuickInteractionStart(mActivity, null, true,
+                    mTouchInteractionLog);
+            mQuickScrubController.onQuickScrubProgress(mLastProgress);
+            mStartPending = false;
+
+            if (mEndPending) {
+                mQuickScrubController.onQuickScrubEnd();
+                mEndPending = false;
+            }
+        };
+
+        if (mWaitForWindowAvailable) {
+            mActivityHelper.executeOnWindowAvailable(mActivity, action);
+        } else {
+            action.run();
+        }
+    }
+
+    @Override
+    public void onQuickScrubEnd() {
+        mTouchInteractionLog.endQuickScrub("onQuickScrubEnd");
+        if (mInvalidated) {
+            return;
+        }
+        if (mStartPending) {
+            mEndPending = true;
+        } else {
+            mQuickScrubController.onQuickScrubEnd();
+        }
+    }
+
+    @Override
+    public void onQuickScrubProgress(float progress) {
+        mTouchInteractionLog.setQuickScrubProgress(progress);
+        mLastProgress = progress;
+        if (mInvalidated || mStartPending) {
+            return;
+        }
+        mQuickScrubController.onQuickScrubProgress(progress);
+    }
+
+    public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
+            boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) {
+        return newInstance(activityHelper, startingInActivityBounds, touchInteractionLog,
+                true /* waitForWindowAvailable */);
+    }
+
+    public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
+            boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog,
+            boolean waitForWindowAvailable) {
+        BaseDraggingActivity activity = activityHelper.getCreatedActivity();
+        if (activity == null) {
+            return TouchConsumer.NO_OP;
+        }
+        return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds,
+                touchInteractionLog, waitForWindowAvailable);
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index db0150e..ab5fce1 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -267,6 +267,10 @@
         return mWaitingForTaskLaunch;
     }
 
+    public boolean hasFinishedTransitionToQuickScrub() {
+        return mFinishedTransitionToQuickScrub;
+    }
+
     /**
      * Attempts to go to normal overview or back to home, so UI doesn't prevent user interaction.
      */
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 026f715..d9b1fcf 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -46,8 +46,6 @@
 
     default void onQuickScrubProgress(float progress) { }
 
-    default void onQuickStep(MotionEvent ev) { }
-
     default void onShowOverviewFromAltTab() {}
 
     default boolean isActive() {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 97e797b..6bbcd65 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -17,37 +17,35 @@
 
 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_INDEX_SHIFT;
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_CHANNEL;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.Service;
 import android.content.Intent;
-import android.graphics.PointF;
+import android.graphics.Region;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Choreographer;
+import android.view.InputEvent;
 import android.view.MotionEvent;
-import android.view.ViewConfiguration;
 
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.MainThreadExecutor;
 import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.views.RecentsView;
 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.InputChannelCompat;
+import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
 
@@ -77,8 +75,29 @@
 
     private final IBinder mMyBinder = new IOverviewProxy.Stub() {
 
-        @Override
+        public void onActiveNavBarRegionChanges(Region region) {
+            mActiveNavBarRegion = region;
+        }
+
+        public void onInitialize(Bundle bundle) {
+            mISystemUiProxy = ISystemUiProxy.Stub
+                    .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
+            mRecentsModel.setSystemUiProxy(mISystemUiProxy);
+            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
+
+            if (mInputEventReceiver != null) {
+                mInputEventReceiver.dispose();
+            }
+            mInputEventReceiver = InputChannelCompat.fromBundle(bundle, KEY_EXTRA_INPUT_CHANNEL,
+                    Looper.getMainLooper(), mMainChoreographer,
+                    TouchInteractionService.this::onInputEvent);
+        }
+
         public void onPreMotionEvent(@HitTarget int downHitTarget) {
+            // If ev are using the new dispatching system, skip the old logic
+            if (mInputEventReceiver != null) {
+                return;
+            }
             mTouchInteractionLog.prepareForNewGesture();
 
             TraceHelper.beginSection("SysUiBinder");
@@ -86,44 +105,49 @@
             TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
         }
 
-        @Override
         public void onMotionEvent(MotionEvent ev) {
+            // If ev are using the new dispatching system, skip the old logic
+            if (mInputEventReceiver != null) {
+                ev.recycle();
+                return;
+            }
             mEventQueue.queue(ev);
 
             int action = ev.getActionMasked();
-            if (action == ACTION_DOWN) {
-                mOverviewInteractionState.setSwipeGestureInitializing(true);
-            } else if (action == ACTION_UP || action == ACTION_CANCEL) {
-                mOverviewInteractionState.setSwipeGestureInitializing(false);
-            }
-
             String name = sMotionEventNames.get(action);
             if (name != null){
                 TraceHelper.partitionSection("SysUiBinder", name);
             }
         }
 
-        @Override
         public void onBind(ISystemUiProxy iSystemUiProxy) {
             mISystemUiProxy = iSystemUiProxy;
             mRecentsModel.setSystemUiProxy(mISystemUiProxy);
             mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
         }
 
-        @Override
         public void onQuickScrubStart() {
+            // If ev are using the new dispatching system, skip the old logic
+            if (mInputEventReceiver != null) {
+                return;
+            }
             mEventQueue.onQuickScrubStart();
-            mOverviewInteractionState.setSwipeGestureInitializing(false);
             TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
         }
 
-        @Override
         public void onQuickScrubProgress(float progress) {
+            // If ev are using the new dispatching system, skip the old logic
+            if (mInputEventReceiver != null) {
+                return;
+            }
             mEventQueue.onQuickScrubProgress(progress);
         }
 
-        @Override
         public void onQuickScrubEnd() {
+            // If ev are using the new dispatching system, skip the old logic
+            if (mInputEventReceiver != null) {
+                return;
+            }
             mEventQueue.onQuickScrubEnd();
             TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
         }
@@ -135,6 +159,11 @@
 
         @Override
         public void onOverviewShown(boolean triggeredFromAltTab) {
+            // If ev are using the new dispatching system, skip the old logic
+            if (mInputEventReceiver != null) {
+                mOverviewCommandHelper.onOverviewShown();
+                return;
+            }
             if (triggeredFromAltTab) {
                 mEventQueue.onNewGesture(HIT_TARGET_NONE);
                 mEventQueue.onOverviewShownFromAltTab();
@@ -145,18 +174,17 @@
 
         @Override
         public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+            // If ev are using the new dispatching system, skip the old logic
+            if (mInputEventReceiver != null) {
+                return;
+            }
             if (triggeredFromAltTab && !triggeredFromHomeKey) {
                 // onOverviewShownFromAltTab initiates quick scrub. Ending it here.
                 mEventQueue.onQuickScrubEnd();
             }
         }
 
-        @Override
-        public void onQuickStep(MotionEvent motionEvent) {
-            mEventQueue.onQuickStep(motionEvent);
-            mOverviewInteractionState.setSwipeGestureInitializing(false);
-            TraceHelper.endSection("SysUiBinder", "onQuickStep");
-        }
+        public void onQuickStep(MotionEvent motionEvent) { }
 
         @Override
         public void onTip(int actionType, int viewType) {
@@ -183,12 +211,19 @@
     private InputConsumerController mInputConsumer;
     private SwipeSharedState mSwipeSharedState;
 
+    private TouchConsumer mConsumer = TouchConsumer.NO_OP;
+    private Choreographer mMainChoreographer;
+    private InputEventReceiver mInputEventReceiver;
+    private Region mActiveNavBarRegion = new Region();
+
     @Override
     public void onCreate() {
         super.onCreate();
         mAM = ActivityManagerWrapper.getInstance();
         mRecentsModel = RecentsModel.INSTANCE.get(this);
         mOverviewComponentObserver = new OverviewComponentObserver(this);
+        mMainChoreographer = Choreographer.getInstance();
+
         mOverviewCommandHelper = new OverviewCommandHelper(this, mOverviewComponentObserver);
         mEventQueue = new MotionEventQueue(Looper.myLooper(), Choreographer.getInstance(),
                 this::newConsumer);
@@ -211,6 +246,9 @@
         mInputConsumer.unregisterInputConsumer();
         mOverviewComponentObserver.onDestroy();
         mEventQueue.dispose();
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dispose();
+        }
         sConnected = false;
         super.onDestroy();
     }
@@ -221,6 +259,22 @@
         return mMyBinder;
     }
 
+    private void onInputEvent(InputEvent ev) {
+        if (!(ev instanceof MotionEvent)) {
+            Log.e(TAG, "Unknown event " + ev);
+            return;
+        }
+        MotionEvent event = (MotionEvent) ev;
+        if (event.getAction() == ACTION_DOWN) {
+            mTouchInteractionLog.prepareForNewGesture();
+            boolean useSharedState = mConsumer.isActive();
+            mConsumer.onConsumerAboutToBeSwitched();
+            mConsumer = newConsumer(useSharedState, event);
+        }
+
+        mConsumer.accept(event);
+    }
+
     private TouchConsumer newConsumer(@HitTarget int downHitTarget, boolean useSharedState) {
         RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
         if (!useSharedState) {
@@ -240,12 +294,54 @@
                     mOverviewComponentObserver.getActivityControlHelper(), false,
                     mTouchInteractionLog, false /* waitForWindowAvailable */);
         } else {
+            ActivityControlHelper activityControl =
+                    mOverviewComponentObserver.getActivityControlHelper();
             return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
                     mOverviewComponentObserver.getOverviewIntent(),
                     mOverviewComponentObserver.getActivityControlHelper(),
-                    downHitTarget, mOverviewCallbacks,
-                    mTaskOverlayFactory, mInputConsumer, mTouchInteractionLog, mEventQueue,
-                    mSwipeSharedState);
+                    activityControl.deferStartingActivity(downHitTarget), mOverviewCallbacks,
+                    mTaskOverlayFactory, mInputConsumer, mTouchInteractionLog,
+                    mEventQueue::onConsumerInactive, mSwipeSharedState);
+        }
+    }
+
+    private TouchConsumer newConsumer(boolean useSharedState, MotionEvent event) {
+        RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
+        if (!useSharedState) {
+            mSwipeSharedState.clearAllState();
+        }
+
+        if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
+            return TouchConsumer.NO_OP;
+        } else if (mSwipeSharedState.goingToLauncher ||
+                mOverviewComponentObserver.getActivityControlHelper().isResumed()) {
+            return OverviewTouchConsumer.newInstance(
+                    mOverviewComponentObserver.getActivityControlHelper(), false,
+                    mTouchInteractionLog);
+        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
+                mOverviewComponentObserver.getActivityControlHelper().isInLiveTileMode()) {
+            return OverviewTouchConsumer.newInstance(
+                    mOverviewComponentObserver.getActivityControlHelper(), false,
+                    mTouchInteractionLog, false /* waitForWindowAvailable */);
+        } else {
+            ActivityControlHelper activityControl =
+                    mOverviewComponentObserver.getActivityControlHelper();
+            boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
+            return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
+                    mOverviewComponentObserver.getOverviewIntent(),
+                    mOverviewComponentObserver.getActivityControlHelper(),
+                    shouldDefer, mOverviewCallbacks,
+                    mTaskOverlayFactory, mInputConsumer, mTouchInteractionLog,
+                    this::onConsumerInactive, mSwipeSharedState);
+        }
+    }
+
+    /**
+     * To be called by the consumer when it's no longer active.
+     */
+    private void onConsumerInactive(TouchConsumer caller) {
+        if (mConsumer == caller) {
+            mConsumer = TouchConsumer.NO_OP;
         }
     }
 
@@ -253,212 +349,4 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mTouchInteractionLog.dump(pw);
     }
-
-    public static class OverviewTouchConsumer<T extends BaseDraggingActivity>
-            implements TouchConsumer {
-
-        private final ActivityControlHelper<T> mActivityHelper;
-        private final T mActivity;
-        private final BaseDragLayer mTarget;
-        private final int[] mLocationOnScreen = new int[2];
-        private final PointF mDownPos = new PointF();
-        private final int mTouchSlop;
-        private final QuickScrubController mQuickScrubController;
-        private final TouchInteractionLog mTouchInteractionLog;
-
-        private final boolean mStartingInActivityBounds;
-
-        private boolean mTrackingStarted = false;
-        private boolean mInvalidated = false;
-
-        private float mLastProgress = 0;
-        private boolean mStartPending = false;
-        private boolean mEndPending = false;
-        private boolean mWaitForWindowAvailable;
-
-        OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity,
-                boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog,
-                boolean waitForWindowAvailable) {
-            mActivityHelper = activityHelper;
-            mActivity = activity;
-            mTarget = activity.getDragLayer();
-            mTouchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop();
-            mStartingInActivityBounds = startingInActivityBounds;
-
-            mQuickScrubController = mActivity.<RecentsView>getOverviewPanel()
-                    .getQuickScrubController();
-            mTouchInteractionLog = touchInteractionLog;
-            mTouchInteractionLog.setTouchConsumer(this);
-
-            mWaitForWindowAvailable = waitForWindowAvailable;
-        }
-
-        @Override
-        public void accept(MotionEvent ev) {
-            if (mInvalidated) {
-                return;
-            }
-            mTouchInteractionLog.addMotionEvent(ev);
-            int action = ev.getActionMasked();
-            if (action == ACTION_DOWN) {
-                if (mStartingInActivityBounds) {
-                    startTouchTracking(ev, false /* updateLocationOffset */);
-                    return;
-                }
-                mTrackingStarted = false;
-                mDownPos.set(ev.getX(), ev.getY());
-            } else if (!mTrackingStarted) {
-                switch (action) {
-                    case ACTION_CANCEL:
-                    case ACTION_UP:
-                        startTouchTracking(ev, true /* updateLocationOffset */);
-                        break;
-                    case ACTION_MOVE: {
-                        float displacement = mActivity.getDeviceProfile().isLandscape ?
-                                ev.getX() - mDownPos.x : ev.getY() - mDownPos.y;
-                        if (Math.abs(displacement) >= mTouchSlop) {
-                            // Start tracking only when mTouchSlop is crossed.
-                            startTouchTracking(ev, true /* updateLocationOffset */);
-                        }
-                    }
-                }
-            }
-
-            if (mTrackingStarted) {
-                sendEvent(ev);
-            }
-
-            if (action == ACTION_UP || action == ACTION_CANCEL) {
-                mInvalidated = true;
-            }
-        }
-
-        private void startTouchTracking(MotionEvent ev, boolean updateLocationOffset) {
-            if (updateLocationOffset) {
-                mTarget.getLocationOnScreen(mLocationOnScreen);
-            }
-
-            // Send down touch event
-            MotionEvent down = MotionEvent.obtainNoHistory(ev);
-            down.setAction(ACTION_DOWN);
-            sendEvent(down);
-
-            mTrackingStarted = true;
-            // Send pointer down for remaining pointers.
-            int pointerCount = ev.getPointerCount();
-            for (int i = 1; i < pointerCount; i++) {
-                down.setAction(ACTION_POINTER_DOWN | (i << ACTION_POINTER_INDEX_SHIFT));
-                sendEvent(down);
-            }
-
-            down.recycle();
-        }
-
-        private void sendEvent(MotionEvent ev) {
-            if (!mTarget.verifyTouchDispatch(this, ev)) {
-                mInvalidated = true;
-                return;
-            }
-            int flags = ev.getEdgeFlags();
-            ev.setEdgeFlags(flags | TouchInteractionService.EDGE_NAV_BAR);
-            ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
-            if (!mTrackingStarted) {
-                mTarget.onInterceptTouchEvent(ev);
-            }
-            mTarget.onTouchEvent(ev);
-            ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
-            ev.setEdgeFlags(flags);
-        }
-
-        @Override
-        public void onQuickStep(MotionEvent ev) {
-            if (mInvalidated) {
-                return;
-            }
-            OverviewCallbacks.get(mActivity).closeAllWindows();
-            ActivityManagerWrapper.getInstance()
-                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-            mTouchInteractionLog.startQuickStep();
-        }
-
-        @Override
-        public void onQuickScrubStart() {
-            if (mInvalidated) {
-                return;
-            }
-            mTouchInteractionLog.startQuickScrub();
-            if (!mQuickScrubController.prepareQuickScrub(TAG)) {
-                mInvalidated = true;
-                mTouchInteractionLog.endQuickScrub("onQuickScrubStart");
-                return;
-            }
-            OverviewCallbacks.get(mActivity).closeAllWindows();
-            ActivityManagerWrapper.getInstance()
-                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-
-            mStartPending = true;
-            Runnable action = () -> {
-                if (!mQuickScrubController.prepareQuickScrub(TAG)) {
-                    mInvalidated = true;
-                    mTouchInteractionLog.endQuickScrub("onQuickScrubStart");
-                    return;
-                }
-                mActivityHelper.onQuickInteractionStart(mActivity, null, true,
-                        mTouchInteractionLog);
-                mQuickScrubController.onQuickScrubProgress(mLastProgress);
-                mStartPending = false;
-
-                if (mEndPending) {
-                    mQuickScrubController.onQuickScrubEnd();
-                    mEndPending = false;
-                }
-            };
-
-            if (mWaitForWindowAvailable) {
-                mActivityHelper.executeOnWindowAvailable(mActivity, action);
-            } else {
-                action.run();
-            }
-        }
-
-        @Override
-        public void onQuickScrubEnd() {
-            mTouchInteractionLog.endQuickScrub("onQuickScrubEnd");
-            if (mInvalidated) {
-                return;
-            }
-            if (mStartPending) {
-                mEndPending = true;
-            } else {
-                mQuickScrubController.onQuickScrubEnd();
-            }
-        }
-
-        @Override
-        public void onQuickScrubProgress(float progress) {
-            mTouchInteractionLog.setQuickScrubProgress(progress);
-            mLastProgress = progress;
-            if (mInvalidated || mStartPending) {
-                return;
-            }
-            mQuickScrubController.onQuickScrubProgress(progress);
-        }
-
-        public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
-                boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) {
-            return newInstance(activityHelper, startingInActivityBounds, touchInteractionLog,
-                    true /* waitForWindowAvailable */);
-        }
-
-        public static TouchConsumer newInstance(ActivityControlHelper activityHelper,
-                boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog,
-                boolean waitForWindowAvailable) {
-            BaseDraggingActivity activity = activityHelper.getCreatedActivity();
-            if (activity == null) {
-                return TouchConsumer.NO_OP;
-            }
-            return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds,
-                    touchInteractionLog, waitForWindowAvailable);
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index bc4e094..b0f055c 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -95,7 +95,6 @@
 import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
 import com.android.quickstep.ActivityControlHelper.LayoutListener;
 import com.android.quickstep.TouchConsumer.InteractionType;
-import com.android.quickstep.TouchInteractionService.OverviewTouchConsumer;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.RemoteAnimationTargetSet;
 import com.android.quickstep.util.SwipeAnimationTargetSet;
@@ -700,7 +699,9 @@
         }
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationWrapper.getController() != null && mLayoutListener != null) {
+            if (mRecentsAnimationWrapper.getController() != null && mLayoutListener != null &&
+                    (mInteractionType == INTERACTION_NORMAL
+                            || !mQuickScrubController.hasFinishedTransitionToQuickScrub())) {
                 mLayoutListener.open();
                 mLayoutListener.update(mCurrentShift.value > 1, mLongSwipeMode,
                         mClipAnimationHelper.getCurrentRectWithInsets(),
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 1156b87..21d8144 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -20,6 +20,7 @@
 import android.os.SystemClock;
 import android.view.MotionEvent;
 
+import com.android.launcher3.Alarm;
 import com.android.launcher3.R;
 
 /**
@@ -32,11 +33,15 @@
     // The bigger this number, the easier it is to trigger the first pause.
     private static final float RAPID_DECELERATION_FACTOR = 0.6f;
 
+    /** If no motion is added for this amount of time, assume the motion has paused. */
+    private static final long FORCE_PAUSE_TIMEOUT = 300;
+
     private final float mSpeedVerySlow;
     private final float mSpeedSomewhatFast;
     private final float mSpeedFast;
     private final float mMinDisplacementForPause;
     private final float mMaxOrthogonalDisplacementForPause;
+    private final Alarm mForcePauseTimeout;
 
     private Long mPreviousTime = null;
     private Float mPreviousPosition = null;
@@ -59,6 +64,8 @@
         mMinDisplacementForPause = res.getDimension(R.dimen.motion_pause_detector_min_displacement);
         mMaxOrthogonalDisplacementForPause = res.getDimension(
                 R.dimen.motion_pause_detector_max_orthogonal_displacement);
+        mForcePauseTimeout = new Alarm();
+        mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
     }
 
     /**
@@ -70,6 +77,7 @@
         if (mOnMotionPauseListener != null) {
             mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
         }
+        mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
     }
 
     /**
@@ -100,6 +108,7 @@
         }
         mPreviousTime = time;
         mPreviousPosition = position;
+        mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
     }
 
     private void checkMotionPaused(float velocity, float prevVelocity,
@@ -129,6 +138,10 @@
         boolean passedMaxOrthogonalDisplacement =
                 totalDisplacement.orthogonal >= mMaxOrthogonalDisplacementForPause;
         isPaused &= passedMinDisplacement && !passedMaxOrthogonalDisplacement;
+        updatePaused(isPaused);
+    }
+
+    private void updatePaused(boolean isPaused) {
         if (mIsPaused != isPaused) {
             mIsPaused = isPaused;
             if (mIsPaused) {
@@ -149,6 +162,7 @@
         mTotalDisplacement.set(0, 0);
         setOnMotionPauseListener(null);
         mIsPaused = mHasEverBeenPaused = false;
+        mForcePauseTimeout.cancelAlarm();
     }
 
     public boolean isPaused() {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
index 3e49568..686e74d 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsAnimationListenerSet.java
@@ -58,7 +58,7 @@
         SwipeAnimationTargetSet targetSet = new SwipeAnimationTargetSet(controller, targets,
                 homeContentInsets, minimizedHomeBounds);
         Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
-            for (SwipeAnimationListener listener : mListeners) {
+            for (SwipeAnimationListener listener : getListeners()) {
                 listener.onRecentsAnimationStart(targetSet);
             }
         });
@@ -67,9 +67,13 @@
     @Override
     public final void onAnimationCanceled() {
         Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> {
-            for (SwipeAnimationListener listener : mListeners) {
+            for (SwipeAnimationListener listener : getListeners()) {
                 listener.onRecentsAnimationCanceled();
             }
         });
     }
+
+    private SwipeAnimationListener[] getListeners() {
+        return mListeners.toArray(new SwipeAnimationListener[mListeners.size()]);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index bbcd425..cbd6b2f 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -78,6 +78,12 @@
 
     public void initialize(Task task, InitializeCallback callback) {
         mTask = task;
+
+        if (task.key.userId != UserHandle.myUserId()) {
+            callback.call(1, task.titleDescription);
+            return;
+        }
+
         Utilities.THREAD_POOL_EXECUTOR.execute(() -> {
             long appUsageLimitTimeMs = -1;
             long appRemainingTimeMs = -1;
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 5c8f53c..1ee5110 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -16,8 +16,10 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_QUICKSTEP_PREVIEW;
+import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
@@ -111,7 +113,10 @@
         mTranslationYFactor = translationFactor;
         setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            redrawLiveTile(false);
+            LauncherState state = mActivity.getStateManager().getState();
+            if (state == OVERVIEW || state == ALL_APPS) {
+                redrawLiveTile(false);
+            }
         }
     }
 
@@ -216,7 +221,11 @@
             int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY()
                     - mTempRect.height());
             if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) {
-                mTempRect.right += offsetX;
+                if (mTempRect.left - offsetX < 0) {
+                    mTempRect.left -= offsetX;
+                } else {
+                    mTempRect.right += offsetX;
+                }
             }
             if (mightNeedToRefill && offsetY > 0) {
                 mTempRect.top -= offsetY;
diff --git a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
index b801b4f..7274090 100644
--- a/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
+++ b/quickstep/tests/src/com/android/quickstep/QuickStepOnOffRule.java
@@ -90,11 +90,16 @@
                     base.evaluate();
                 }
 
-                private void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride) {
+                private void overrideSwipeUpEnabled(Boolean swipeUpEnabledOverride)
+                        throws Throwable {
                     mLauncher.overrideSwipeUpEnabled(swipeUpEnabledOverride);
                     mMainThreadExecutor.execute(() -> OverviewInteractionState.INSTANCE.get(
                             InstrumentationRegistry.getInstrumentation().getTargetContext()).
                             notifySwipeUpSettingChanged(mLauncher.isSwipeUpEnabled()));
+                    // TODO(b/124236673): avoid using sleep().
+                    mLauncher.getDevice().waitForIdle();
+                    Thread.sleep(2000);
+                    mLauncher.getDevice().waitForIdle();
                 }
             };
         } else {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 245e470..cf16759 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -259,7 +259,7 @@
 
 
     private final Handler mHandler = new Handler();
-    private final Runnable mLogOnDelayedResume = this::logOnDelayedResume;
+    private final Runnable mHandleDeferredResume = this::handleDeferredResume;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -782,11 +782,13 @@
         RaceConditionTracker.onEvent(ON_START_EVT, EXIT);
     }
 
-    private void logOnDelayedResume() {
+    private void handleDeferredResume() {
         if (hasBeenResumed()) {
             getUserEventDispatcher().logActionCommand(Action.Command.RESUME,
                     mStateManager.getState().containerType, -1);
             getUserEventDispatcher().startSession();
+
+            UiFactory.onLauncherStateOrResumeChanged(this);
         }
     }
 
@@ -797,8 +799,8 @@
         super.onResume();
         TraceHelper.partitionSection("ON_RESUME", "superCall");
 
-        mHandler.removeCallbacks(mLogOnDelayedResume);
-        Utilities.postAsyncCallback(mHandler, mLogOnDelayedResume);
+        mHandler.removeCallbacks(mHandleDeferredResume);
+        Utilities.postAsyncCallback(mHandler, mHandleDeferredResume);
 
         setOnResumeCallback(null);
         // Process any items that were added while Launcher was away.
@@ -812,7 +814,6 @@
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onResume();
         }
-        UiFactory.onLauncherStateOrResumeChanged(this);
 
         TraceHelper.endSection("ON_RESUME");
         RaceConditionTracker.onEvent(ON_RESUME_EVT, EXIT);
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 7e2c966..481281a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -74,8 +74,11 @@
      */
     public Workspace dragToWorkspace() {
         final UiDevice device = mLauncher.getDevice();
-        mObject.drag(new Point(
-                device.getDisplayWidth() / 2, device.getDisplayHeight() / 2), DRAG_SPEED);
+        Workspace.dragIconToWorkspace(
+                mLauncher,
+                this,
+                new Point(device.getDisplayWidth() / 2, device.getDisplayHeight() / 2),
+                DRAG_SPEED);
         return new Workspace(mLauncher);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 466bc5e..6b76012 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -266,6 +266,16 @@
                 event -> true,
                 "Pressing Home didn't produce any events");
         mDevice.waitForIdle();
+
+        // Temporarily press home twice as the first click sometimes gets ignored  (b/124239413)
+        executeAndWaitForEvent(
+                () -> {
+                    log("LauncherInstrumentation.pressHome before clicking");
+                    getSystemUiObject("home").click();
+                },
+                event -> true,
+                "Pressing Home didn't produce any events");
+        mDevice.waitForIdle();
         return getWorkspace();
     }
 
@@ -402,7 +412,7 @@
     }
 
     @NonNull
-    UiDevice getDevice() {
+    public UiDevice getDevice() {
         return mDevice;
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 587c712..e10c4fb 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -96,7 +96,13 @@
     public void ensureWorkspaceIsScrollable() {
         final UiObject2 workspace = verifyActiveContainer();
         if (!isWorkspaceScrollable(workspace)) {
-            dragIconToNextScreen(getHotseatAppIcon("Messages"), workspace);
+            dragIconToWorkspace(
+                    mLauncher,
+                    getHotseatAppIcon("Messages"),
+                    new Point(mLauncher.getDevice().getDisplayWidth(),
+                            workspace.getVisibleBounds().centerY()),
+                    ICON_DRAG_SPEED);
+            verifyActiveContainer();
         }
         assertTrue("Home screen workspace didn't become scrollable",
                 isWorkspaceScrollable(workspace));
@@ -112,12 +118,10 @@
                 mHotseat, AppIcon.getAppIconSelector(appName, mLauncher)));
     }
 
-    private void dragIconToNextScreen(AppIcon app, UiObject2 workspace) {
-        final Point dest = new Point(
-                mLauncher.getDevice().getDisplayWidth(), workspace.getVisibleBounds().centerY());
-        app.getObject().drag(dest, ICON_DRAG_SPEED);
-        mLauncher.waitUntilGone("drop_target_bar");
-        verifyActiveContainer();
+    static void dragIconToWorkspace(LauncherInstrumentation launcher, Launchable launchable,
+            Point dest, int icon_drag_speed) {
+        launchable.getObject().drag(dest, icon_drag_speed);
+        launcher.waitUntilGone("drop_target_bar");
     }
 
     /**