Merge changes Id401f273,I0b21c8ea,Ieb7936bd into ub-launcher3-qt-dev

* changes:
  Layout aligned to dp grid for portrait (3/3)
  Layout aligned to dp grid for portrait (2/3)
  Layout aligned to dp grid for portrait (1/3)
diff --git a/Android.mk b/Android.mk
index 6568a26..7956d28 100644
--- a/Android.mk
+++ b/Android.mk
@@ -298,7 +298,7 @@
 LOCAL_PACKAGE_NAME := Launcher3GoIconRecents
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_PRODUCT_MODULE := true
-LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep Launcher3QuickStepGo
+LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep Launcher3QuickStepGo
 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index f991435..61c576e 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -21,4 +21,6 @@
     <!-- The size of corner radius of the arrow in the arrow toast. -->
     <dimen name="arrow_toast_corner_radius">2dp</dimen>
 
+    <!-- Minimum distance to swipe to trigger accessibility gesture -->
+    <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java
new file mode 100644
index 0000000..8f8cd18
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java
@@ -0,0 +1,156 @@
+/*
+ * 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_UP;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.R;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Touch consumer for two finger swipe actions for accessibility actions
+ */
+public class AccessibilityInputConsumer extends DelegateInputConsumer {
+
+    private static final String TAG = "A11yInputConsumer";
+
+    private final ISystemUiProxy mSystemUiProxy;
+    private final VelocityTracker mVelocityTracker;
+    private final MotionPauseDetector mMotionPauseDetector;
+    private final boolean mAllowLongClick;
+
+    private final float mMinGestureDistance;
+    private final float mMinFlingVelocity;
+
+    private int mActivePointerId = -1;
+    private float mDownY;
+    private float mTotalY;
+
+    public AccessibilityInputConsumer(Context context, ISystemUiProxy systemUiProxy,
+            boolean allowLongClick, InputConsumer delegate, InputMonitorCompat inputMonitor) {
+        super(delegate, inputMonitor);
+        mSystemUiProxy = systemUiProxy;
+        mVelocityTracker = VelocityTracker.obtain();
+        mMinGestureDistance = context.getResources()
+                .getDimension(R.dimen.accessibility_gesture_min_swipe_distance);
+        mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+
+        mMotionPauseDetector = new MotionPauseDetector(context);
+        mAllowLongClick = allowLongClick;
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_ACCESSIBILITY | mDelegate.getType();
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        if (mState != STATE_DELEGATE_ACTIVE) {
+            mVelocityTracker.addMovement(ev);
+        }
+
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                break;
+            }
+            case ACTION_POINTER_UP: {
+                if (mState == STATE_ACTIVE) {
+                    int pointerIndex = ev.getActionIndex();
+                    int pointerId = ev.getPointerId(pointerIndex);
+                    if (pointerId == mActivePointerId) {
+                        final int newPointerIdx = pointerIndex == 0 ? 1 : 0;
+
+                        mTotalY += (ev.getY(pointerIndex) - mDownY);
+                        mDownY = ev.getY(newPointerIdx);
+                        mActivePointerId = ev.getPointerId(newPointerIdx);
+                    }
+                }
+                break;
+            }
+            case ACTION_POINTER_DOWN: {
+                if (mState == STATE_INACTIVE) {
+                    if (mDelegate.allowInterceptByParent()) {
+                        setActive(ev);
+
+                        int pointerIndex = ev.getActionIndex();
+                        mActivePointerId = ev.getPointerId(pointerIndex);
+                        mDownY = ev.getY(pointerIndex);
+                    } else {
+                        mState = STATE_DELEGATE_ACTIVE;
+                    }
+                }
+                break;
+            }
+            case ACTION_MOVE: {
+                if (mState == STATE_ACTIVE && mAllowLongClick) {
+                    int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                    if (pointerIndex == -1) {
+                        break;
+                    }
+
+                    mMotionPauseDetector.addPosition(ev.getY(pointerIndex) - mDownY,
+                            ev.getEventTime());
+                }
+                break;
+            }
+            case ACTION_UP:
+                if (mState == STATE_ACTIVE) {
+                    try {
+                        if (mAllowLongClick && mMotionPauseDetector.isPaused()) {
+                            mSystemUiProxy.notifyAccessibilityButtonLongClicked();
+                        } else {
+                            mTotalY += (ev.getY() - mDownY);
+                            mVelocityTracker.computeCurrentVelocity(1000);
+
+                            if ((-mTotalY) > mMinGestureDistance
+                                    || (-mVelocityTracker.getYVelocity()) > mMinFlingVelocity) {
+                                mSystemUiProxy.notifyAccessibilityButtonClicked(
+                                        Display.DEFAULT_DISPLAY);
+                            }
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Unable to notify accessibility event", e);
+                    }
+                }
+                // Follow through
+            case ACTION_CANCEL: {
+                mVelocityTracker.recycle();
+                mMotionPauseDetector.clear();
+                break;
+            }
+        }
+
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
index 5e7faf7..829e478 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
@@ -50,20 +50,10 @@
 /**
  * Touch consumer for handling events to launch assistant from launcher
  */
-public class AssistantTouchConsumer implements InputConsumer {
+public class AssistantTouchConsumer extends DelegateInputConsumer {
     private static final String TAG = "AssistantTouchConsumer";
     private static final long RETRACT_ANIMATION_DURATION_MS = 300;
 
-    /* The assistant touch consume competes with quick switch InputConsumer gesture. The delegate
-     * can be chosen to run if the angle passing the slop is lower than the threshold angle. When
-     * this occurs, the state changes to {@link #STATE_DELEGATE_ACTIVE} where the next incoming
-     * motion events are handled by the delegate instead of the assistant touch consumer. If the
-     * angle is higher than the threshold, the state will change to {@link #STATE_ASSISTANT_ACTIVE}.
-     */
-    private static final int STATE_INACTIVE = 0;
-    private static final int STATE_ASSISTANT_ACTIVE = 1;
-    private static final int STATE_DELEGATE_ACTIVE = 2;
-
     private static final String INVOCATION_TYPE_KEY = "invocation_type";
     private static final int INVOCATION_TYPE_GESTURE = 1;
 
@@ -78,7 +68,6 @@
     private float mTimeFraction;
     private long mDragTime;
     private float mLastProgress;
-    private int mState;
     private int mDirection;
     private ActivityControlHelper mActivityControlHelper;
 
@@ -87,46 +76,25 @@
     private final int mAngleThreshold;
     private final float mSlop;
     private final ISystemUiProxy mSysUiProxy;
-    private final InputConsumer mConsumerDelegate;
     private final Context mContext;
 
-    private final InputMonitorCompat mInputMonitorCompat;
-
-
     public AssistantTouchConsumer(Context context, ISystemUiProxy systemUiProxy,
-            InputConsumer delegate, InputMonitorCompat inputMonitorCompat,
-            ActivityControlHelper activityControlHelper) {
+            ActivityControlHelper activityControlHelper, InputConsumer delegate,
+            InputMonitorCompat inputMonitor) {
+        super(delegate, inputMonitor);
         final Resources res = context.getResources();
         mContext = context;
         mSysUiProxy = systemUiProxy;
-        mConsumerDelegate = delegate;
         mDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
         mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
         mAngleThreshold = res.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
         mSlop = QuickStepContract.getQuickStepDragSlopPx();
-        mInputMonitorCompat = inputMonitorCompat;
         mActivityControlHelper = activityControlHelper;
-        mState = STATE_INACTIVE;
     }
 
     @Override
     public int getType() {
-        return TYPE_ASSISTANT;
-    }
-
-    @Override
-    public boolean useSharedSwipeState() {
-        if (mConsumerDelegate != null) {
-            return mConsumerDelegate.useSharedSwipeState();
-        }
-        return false;
-    }
-
-    @Override
-    public void onConsumerAboutToBeSwitched() {
-        if (mConsumerDelegate != null) {
-            mConsumerDelegate.onConsumerAboutToBeSwitched();
-        }
+        return TYPE_ASSISTANT | mDelegate.getType();
     }
 
     @Override
@@ -158,6 +126,10 @@
                 if (mState == STATE_DELEGATE_ACTIVE) {
                     break;
                 }
+                if (!mDelegate.allowInterceptByParent()) {
+                    mState = STATE_DELEGATE_ACTIVE;
+                    break;
+                }
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
                 if (pointerIndex == -1) {
                     break;
@@ -168,9 +140,6 @@
                     // Normal gesture, ensure we pass the slop before we start tracking the gesture
                     if (Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) > mSlop) {
 
-                        // Cancel touches to other windows (intercept)
-                        mInputMonitorCompat.pilferPointers();
-
                         mPassedSlop = true;
                         mStartDragPos.set(mLastPos.x, mLastPos.y);
                         mDragTime = SystemClock.uptimeMillis();
@@ -182,15 +151,7 @@
                         angle = angle > 90 ? 180 - angle : angle;
 
                         if (angle > mAngleThreshold && angle < 90) {
-                            mState = STATE_ASSISTANT_ACTIVE;
-
-                            if (mConsumerDelegate != null) {
-                                // Send cancel event
-                                MotionEvent event = MotionEvent.obtain(ev);
-                                event.setAction(MotionEvent.ACTION_CANCEL);
-                                mConsumerDelegate.onMotionEvent(event);
-                                event.recycle();
-                            }
+                            setActive(ev);
                         } else {
                             mState = STATE_DELEGATE_ACTIVE;
                         }
@@ -232,8 +193,8 @@
                 break;
         }
 
-        if (mState != STATE_ASSISTANT_ACTIVE && mConsumerDelegate != null) {
-            mConsumerDelegate.onMotionEvent(ev);
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
         }
     }
 
@@ -249,7 +210,8 @@
                     Bundle args = new Bundle();
                     args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
 
-                    BaseDraggingActivity launcherActivity = mActivityControlHelper.getCreatedActivity();
+                    BaseDraggingActivity launcherActivity =
+                            mActivityControlHelper.getCreatedActivity();
                     if (launcherActivity != null) {
                         launcherActivity.getRootView().
                                 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java
new file mode 100644
index 0000000..d36162f
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java
@@ -0,0 +1,49 @@
+package com.android.quickstep;
+
+import android.view.MotionEvent;
+
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+public abstract class DelegateInputConsumer implements InputConsumer {
+
+    protected static final int STATE_INACTIVE = 0;
+    protected static final int STATE_ACTIVE = 1;
+    protected static final int STATE_DELEGATE_ACTIVE = 2;
+
+    protected final InputConsumer mDelegate;
+    protected final InputMonitorCompat mInputMonitor;
+
+    protected int mState;
+
+    public DelegateInputConsumer(InputConsumer delegate, InputMonitorCompat inputMonitor) {
+        mDelegate = delegate;
+        mInputMonitor = inputMonitor;
+        mState = STATE_INACTIVE;
+    }
+
+    @Override
+    public boolean useSharedSwipeState() {
+        return mDelegate.useSharedSwipeState();
+    }
+
+    @Override
+    public boolean allowInterceptByParent() {
+        return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
+    }
+
+    @Override
+    public void onConsumerAboutToBeSwitched() {
+        mDelegate.onConsumerAboutToBeSwitched();
+    }
+
+    protected void setActive(MotionEvent ev) {
+        mState = STATE_ACTIVE;
+        mInputMonitor.pilferPointers();
+
+        // Send cancel event
+        MotionEvent event = MotionEvent.obtain(ev);
+        event.setAction(MotionEvent.ACTION_CANCEL);
+        mDelegate.onMotionEvent(event);
+        event.recycle();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
index e3f9e02..37b7288 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
@@ -24,11 +24,12 @@
 @TargetApi(Build.VERSION_CODES.O)
 public interface InputConsumer {
 
-    int TYPE_NO_OP = 0;
-    int TYPE_OVERVIEW = 1;
-    int TYPE_OTHER_ACTIVITY = 2;
-    int TYPE_ASSISTANT = 3;
-    int TYPE_DEVICE_LOCKED = 4;
+    int TYPE_NO_OP = 1 << 0;
+    int TYPE_OVERVIEW = 1 << 1;
+    int TYPE_OTHER_ACTIVITY = 1 << 2;
+    int TYPE_ASSISTANT = 1 << 3;
+    int TYPE_DEVICE_LOCKED = 1 << 4;
+    int TYPE_ACCESSIBILITY = 1 << 5;
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
 
@@ -39,6 +40,13 @@
     }
 
     /**
+     * Returns true if the user has crossed the threshold for it to be an explicit action.
+     */
+    default boolean allowInterceptByParent() {
+        return true;
+    }
+
+    /**
      * Called by the event queue when the consumer is about to be switched to a new consumer.
      */
     default void onConsumerAboutToBeSwitched() { }
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 a033402..3d2659d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -308,7 +308,10 @@
         SCALE_PROPERTY.set(recentsView, targetRvScale);
         recentsView.setTranslationY(0);
         ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
+        float tmpCurveScale = v.getCurveScale();
+        v.setCurveScale(1f);
         clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
+        v.setCurveScale(tmpCurveScale);
         SCALE_PROPERTY.set(recentsView, prevRvScale);
         recentsView.setTranslationY(prevRvTransY);
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index 5dc641f..507535e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -21,6 +21,7 @@
 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.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.util.RaceConditionTracker.ENTER;
 import static com.android.launcher3.util.RaceConditionTracker.EXIT;
@@ -345,18 +346,21 @@
      */
     private void finishTouchTracking(MotionEvent ev) {
         if (mPassedDragSlop && mInteractionHandler != null) {
+            if (ev.getActionMasked() == ACTION_CANCEL) {
+                mInteractionHandler.onGestureCancelled();
+            } else {
+                mVelocityTracker.computeCurrentVelocity(1000,
+                        ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
+                float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
+                float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
+                float velocity = isNavBarOnRight() ? velocityX
+                        : isNavBarOnLeft() ? -velocityX
+                                : velocityY;
 
-            mVelocityTracker.computeCurrentVelocity(1000,
-                    ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
-            float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
-            float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
-            float velocity = isNavBarOnRight() ? velocityX
-                    : isNavBarOnLeft() ? -velocityX
-                            : velocityY;
-
-            mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
-            mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY),
-                    mDownPos);
+                mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
+                mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY),
+                        mDownPos);
+            }
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
             // starting the gesture. In that case, just cleanup immediately.
@@ -387,7 +391,7 @@
             mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
             mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
             if (mSwipeSharedState.canGestureBeContinued) {
-                mInteractionHandler.cancel();
+                mInteractionHandler.cancelCurrentAnimation();
             } else {
                 mInteractionHandler.reset();
             }
@@ -425,4 +429,9 @@
     public boolean useSharedSwipeState() {
         return mInteractionHandler != null;
     }
+
+    @Override
+    public boolean allowInterceptByParent() {
+        return !mPassedTouchSlop;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
index b803071..b48e3de 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
@@ -15,24 +15,20 @@
  */
 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_UP;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
-import android.graphics.PointF;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+import androidx.annotation.Nullable;
 
 /**
  * Input consumer for handling touch on the recents/Launcher activity.
@@ -40,24 +36,27 @@
 public class OverviewInputConsumer<T extends BaseDraggingActivity>
         implements InputConsumer {
 
-    private final CachedEventDispatcher mCachedEventDispatcher = new CachedEventDispatcher();
     private final T mActivity;
     private final BaseDragLayer mTarget;
+    private final InputMonitorCompat mInputMonitor;
+
     private final int[] mLocationOnScreen = new int[2];
-    private final PointF mDownPos = new PointF();
-    private final int mTouchSlopSquared;
+    private final boolean mProxyTouch;
 
     private final boolean mStartingInActivityBounds;
+    private boolean mTargetHandledTouch;
 
-    private boolean mTrackingStarted = false;
-    private boolean mInvalidated = false;
-
-    OverviewInputConsumer(T activity, boolean startingInActivityBounds) {
+    OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
+            boolean startingInActivityBounds) {
         mActivity = activity;
-        mTarget = activity.getDragLayer();
-        int touchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop();
-        mTouchSlopSquared = touchSlop * touchSlop;
+        mInputMonitor = inputMonitor;
         mStartingInActivityBounds = startingInActivityBounds;
+
+        mTarget = activity.getDragLayer();
+        if (!startingInActivityBounds) {
+            mTarget.getLocationOnScreen(mLocationOnScreen);
+        }
+        mProxyTouch = mTarget.prepareProxyEventStarting();
     }
 
     @Override
@@ -66,46 +65,35 @@
     }
 
     @Override
+    public boolean allowInterceptByParent() {
+        return !mTargetHandledTouch;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
-        if (mInvalidated) {
+        if (!mProxyTouch) {
             return;
         }
-        mCachedEventDispatcher.dispatchEvent(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 x = ev.getX() - mDownPos.x;
-                    float y = ev.getY() - mDownPos.y;
-                    double hypotSquared = x * x + y * y;
-                    if (hypotSquared >= mTouchSlopSquared) {
-                        // Start tracking only when touch slop is crossed.
-                        startTouchTracking(ev, true /* updateLocationOffset */,
-                                true /* closeActiveWindows */);
-                    }
-                }
-            }
+
+        int flags = ev.getEdgeFlags();
+        if (!mStartingInActivityBounds) {
+            ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
         }
+        ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
+        boolean handled = mTarget.proxyTouchEvent(ev);
+        ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
+        ev.setEdgeFlags(flags);
 
-        if (action == ACTION_UP || action == ACTION_CANCEL) {
-            mInvalidated = true;
-
-            // Set an empty consumer to that all the cached events are cleared
-            if (!mCachedEventDispatcher.hasConsumer()) {
-                mCachedEventDispatcher.setConsumer(motionEvent -> { });
+        if (!mTargetHandledTouch && handled) {
+            mTargetHandledTouch = true;
+            if (!mStartingInActivityBounds) {
+                OverviewCallbacks.get(mActivity).closeAllWindows();
+                ActivityManagerWrapper.getInstance()
+                        .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+                TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+            }
+            if (mInputMonitor != null) {
+                mInputMonitor.pilferPointers();
             }
         }
     }
@@ -117,42 +105,12 @@
         }
     }
 
-    private void startTouchTracking(MotionEvent ev, boolean updateLocationOffset,
-            boolean closeActiveWindows) {
-        if (updateLocationOffset) {
-            mTarget.getLocationOnScreen(mLocationOnScreen);
-        }
-
-        if (closeActiveWindows) {
-            OverviewCallbacks.get(mActivity).closeAllWindows();
-            ActivityManagerWrapper.getInstance()
-                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-            TOUCH_INTERACTION_LOG.addLog("startQuickstep");
-        }
-
-        mTrackingStarted = true;
-        mCachedEventDispatcher.setConsumer(this::sendEvent);
-
-    }
-
-    private void sendEvent(MotionEvent ev) {
-        if (mInvalidated) {
-            return;
-        }
-        int flags = ev.getEdgeFlags();
-        ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
-        ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
-        mInvalidated = !mTarget.dispatchTouchEvent(this, ev);
-        ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
-        ev.setEdgeFlags(flags);
-    }
-
     public static InputConsumer newInstance(ActivityControlHelper activityHelper,
-            boolean startingInActivityBounds) {
+            @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
         BaseDraggingActivity activity = activityHelper.getCreatedActivity();
         if (activity == null) {
             return InputConsumer.NO_OP;
         }
-        return new OverviewInputConsumer(activity, startingInActivityBounds);
+        return new OverviewInputConsumer(activity, inputMonitor, startingInActivityBounds);
     }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
index 7c6638a..f393387 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java
@@ -51,9 +51,16 @@
         mLastAnimationRunning = true;
     }
 
+    private void clearAnimationTarget() {
+        if (mLastAnimationTarget != null) {
+            mLastAnimationTarget.release();
+            mLastAnimationTarget = null;
+        }
+    }
+
     @Override
     public final void onRecentsAnimationCanceled() {
-        mLastAnimationTarget = null;
+        clearAnimationTarget();
 
         mLastAnimationCancelled = true;
         mLastAnimationRunning = false;
@@ -64,7 +71,7 @@
             mRecentsAnimationListener.removeListener(this);
         }
         mRecentsAnimationListener = null;
-        mLastAnimationTarget = null;
+        clearAnimationTarget();
         mLastAnimationCancelled = false;
         mLastAnimationRunning = false;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 4526d67..5b94002 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -19,6 +19,8 @@
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
 import android.graphics.RectF;
@@ -109,8 +111,14 @@
      */
     public static ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
             RemoteAnimationTargetCompat[] targets, final ClipAnimationHelper inOutHelper) {
+        SyncRtSurfaceTransactionApplierCompat applier =
+                new SyncRtSurfaceTransactionApplierCompat(v);
         ClipAnimationHelper.TransformParams params = new ClipAnimationHelper.TransformParams()
-                .setSyncTransactionApplier(new SyncRtSurfaceTransactionApplierCompat(v));
+                .setSyncTransactionApplier(applier);
+
+        final RemoteAnimationTargetSet targetSet =
+                new RemoteAnimationTargetSet(targets, MODE_OPENING);
+        targetSet.addDependentTransactionApplier(applier);
 
         final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
         appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
@@ -120,17 +128,15 @@
             final FloatProp mViewAlpha = new FloatProp(1f, 0f, 75, 75, LINEAR);
             final FloatProp mTaskAlpha = new FloatProp(0f, 1f, 0, 75, LINEAR);
 
-            final RemoteAnimationTargetSet mTargetSet;
 
             final RectF mThumbnailRect;
 
             {
-                mTargetSet = new RemoteAnimationTargetSet(targets, MODE_OPENING);
                 inOutHelper.setTaskAlphaCallback((t, alpha) -> mTaskAlpha.value);
 
                 inOutHelper.prepareAnimation(true /* isOpening */);
                 inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
-                        mTargetSet.apps.length == 0 ? null : mTargetSet.apps[0]);
+                        targetSet.apps.length == 0 ? null : targetSet.apps[0]);
 
                 mThumbnailRect = new RectF(inOutHelper.getTargetRect());
                 mThumbnailRect.offset(-v.getTranslationX(), -v.getTranslationY());
@@ -140,7 +146,7 @@
             @Override
             public void onUpdate(float percent) {
                 params.setProgress(1 - percent);
-                RectF taskBounds = inOutHelper.applyTransform(mTargetSet, params);
+                RectF taskBounds = inOutHelper.applyTransform(targetSet, params);
                 if (!skipViewChanges) {
                     float scale = taskBounds.width() / mThumbnailRect.width();
                     v.setScaleX(scale);
@@ -151,6 +157,12 @@
                 }
             }
         });
+        appAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                targetSet.release();
+            }
+        });
         return appAnimator;
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index fc3f332..c91bb1b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -20,6 +20,8 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -67,9 +69,8 @@
 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 java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -456,25 +457,32 @@
         final ActivityControlHelper activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
 
+        InputConsumer base;
         if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
-            return InputConsumer.NO_OP;
-        } else if (mAssistantAvailable
-                && SysUINavigationMode.INSTANCE.get(this).getMode() == Mode.NO_BUTTON
-                && AssistantTouchConsumer.withinTouchRegion(this, event)) {
-
-            boolean addDelegate = !activityControl.isResumed();
-            return new AssistantTouchConsumer(this, mISystemUiProxy, addDelegate ?
-                    createOtherActivityInputConsumer(event, runningTaskInfo) : null,
-                    mInputMonitorCompat, activityControl);
-
+            base = InputConsumer.NO_OP;
         } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
-            return OverviewInputConsumer.newInstance(activityControl, false);
+            base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
                 activityControl.isInLiveTileMode()) {
-            return OverviewInputConsumer.newInstance(activityControl, false);
+            base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
         } else {
-            return createOtherActivityInputConsumer(event, runningTaskInfo);
+            base = createOtherActivityInputConsumer(event, runningTaskInfo);
         }
+
+        if (mMode == Mode.NO_BUTTON) {
+            if (mAssistantAvailable && AssistantTouchConsumer.withinTouchRegion(this, event)) {
+                base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base,
+                        mInputMonitorCompat);
+            }
+
+            if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
+                base = new AccessibilityInputConsumer(this, mISystemUiProxy,
+                        (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
+                        mInputMonitorCompat);
+            }
+        }
+
+        return base;
     }
 
     private OtherActivityInputConsumer createOtherActivityInputConsumer(MotionEvent event,
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 afc4fcb..3797e87 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -250,7 +250,6 @@
 
     private T mActivity;
     private RecentsView mRecentsView;
-    private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
     private AnimationFactory mAnimationFactory = (t) -> { };
     private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
 
@@ -405,8 +404,11 @@
         }
 
         mRecentsView = activity.getOverviewPanel();
-        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView,
-                applier ->  mSyncTransactionApplier = applier );
+        SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> {
+            mTransformParams.setSyncTransactionApplier(applier);
+            mRecentsAnimationWrapper.runOnInit(() ->
+                    mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier));
+            });
         mRecentsView.setEnableFreeScroll(false);
 
         mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
@@ -648,8 +650,7 @@
             }
             float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
                     mClipAnimationHelper.getTargetRect().width());
-            mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale)
-                    .setSyncTransactionApplier(mSyncTransactionApplier);
+            mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale);
             mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet,
                     mTransformParams);
             mRecentsAnimationWrapper.setWindowThresholdCrossed(
@@ -769,6 +770,17 @@
     }
 
     /**
+     * Called as a result on ACTION_CANCEL to return the UI to the start state.
+     */
+    @UiThread
+    public void onGestureCancelled() {
+        updateDisplacement(0);
+        setStateOnUiThread(STATE_GESTURE_COMPLETED);
+        mLogAction = Touch.SWIPE_NOOP;
+        handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
+    }
+
+    /**
      * @param endVelocity The velocity in the direction of the nav bar to the middle of the screen.
      * @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.
@@ -788,7 +800,7 @@
             mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
         }
         mDownPos = downPos;
-        handleNormalGestureEnd(endVelocity, isFling, velocity);
+        handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
     @UiThread
@@ -802,11 +814,12 @@
             setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
         }
 
-        return OverviewInputConsumer.newInstance(mActivityControlHelper, true);
+        return OverviewInputConsumer.newInstance(mActivityControlHelper, null, true);
     }
 
     @UiThread
-    private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity) {
+    private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
+            boolean isCancel) {
         PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
@@ -824,7 +837,9 @@
         }
         final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW;
         if (!isFling) {
-            if (mMode == Mode.NO_BUTTON) {
+            if (isCancel) {
+                endTarget = LAST_TASK;
+            } else if (mMode == Mode.NO_BUTTON) {
                 if (mIsShelfPeeking) {
                     endTarget = RECENTS;
                 } else if (goingToNewTask) {
@@ -907,7 +922,7 @@
 
     private void doLogGesture(GestureEndTarget endTarget) {
         DeviceProfile dp = mDp;
-        if (dp == null) {
+        if (dp == null || mDownPos == null) {
             // We probably never received an animation controller, skip logging.
             return;
         }
@@ -1047,8 +1062,7 @@
 
             float iconAlpha = Utilities.mapToRange(interpolatedProgress, 0,
                     windowAlphaThreshold, 0f, 1f, Interpolators.LINEAR);
-            mTransformParams.setCurrentRectAndTargetAlpha(currentRect, 1f - iconAlpha)
-                    .setSyncTransactionApplier(mSyncTransactionApplier);
+            mTransformParams.setCurrentRectAndTargetAlpha(currentRect, 1f - iconAlpha);
             mClipAnimationHelper.applyTransform(targetSet, mTransformParams,
                     false /* launcherOnTop */);
 
@@ -1106,7 +1120,11 @@
         setStateOnUiThread(STATE_HANDLER_INVALIDATED);
     }
 
-    public void cancel() {
+    /**
+     * 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() {
         mCurrentShift.cancelAnimation();
         if (mLauncherTransitionController != null && mLauncherTransitionController
                 .getAnimationPlayer().isStarted()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 777e592..09d323e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -17,18 +17,13 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.ViewDebug;
 import android.view.WindowInsets;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
@@ -39,9 +34,6 @@
     private static final int MIN_SIZE = 10;
     private final RecentsActivity mActivity;
 
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final RectF mTouchExcludeRegion = new RectF();
-
     private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
 
     public RecentsRootView(Context context, AttributeSet attrs) {
@@ -100,26 +92,7 @@
 
     @Override
     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
-        if (Utilities.ATLEAST_Q) {
-            Insets gestureInsets = insets.getMandatorySystemGestureInsets();
-            mTouchExcludeRegion.set(gestureInsets.left, gestureInsets.top,
-                    gestureInsets.right, gestureInsets.bottom);
-        }
+        updateTouchExcludeRegion(insets);
         return super.dispatchApplyWindowInsets(insets);
     }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            float x = ev.getX();
-            float y = ev.getY();
-            if (y < mTouchExcludeRegion.top
-                    || x < mTouchExcludeRegion.left
-                    || x > (getWidth() - mTouchExcludeRegion.right)
-                    || y > (getHeight() - mTouchExcludeRegion.bottom)) {
-                return false;
-            }
-        }
-        return super.dispatchTouchEvent(ev);
-    }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index 5a1a103..83973fa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -95,9 +95,4 @@
 
         void onRecentsAnimationCanceled();
     }
-
-    public interface SwipeAnimationFinishListener {
-
-        void onSwipeAnimationFinished(SwipeAnimationTargetSet targetSet);
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 848c214..298c562 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -492,7 +492,7 @@
         return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
     }
 
-    private void setCurveScale(float curveScale) {
+    public void setCurveScale(float curveScale) {
         mCurveScale = curveScale;
         onScaleChanged();
     }
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index cda9d4f..886dcc3 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -416,10 +416,9 @@
 
         RemoteAnimationTargetSet openingTargets = new RemoteAnimationTargetSet(targets,
                 MODE_OPENING);
-        RemoteAnimationTargetSet closingTargets = new RemoteAnimationTargetSet(targets,
-                MODE_CLOSING);
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
                 new SyncRtSurfaceTransactionApplierCompat(mFloatingView);
+        openingTargets.addDependentTransactionApplier(surfaceApplier);
 
         // Scale the app icon to take up the entire screen. This simplifies the math when
         // animating the app window position / scale.
@@ -470,6 +469,7 @@
                 if (v instanceof BubbleTextView) {
                     ((BubbleTextView) v).setStayPressed(false);
                 }
+                openingTargets.release();
             }
         });
 
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
index c372485..0df4e94 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
@@ -16,14 +16,20 @@
 package com.android.quickstep.util;
 
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Queue;
 
 /**
  * Holds a collection of RemoteAnimationTargets, filtered by different properties.
  */
 public class RemoteAnimationTargetSet {
 
+    private final Queue<SyncRtSurfaceTransactionApplierCompat> mDependentTransactionAppliers =
+            new ArrayDeque<>(1);
+
     public final RemoteAnimationTargetCompat[] unfilteredApps;
     public final RemoteAnimationTargetCompat[] apps;
     public final int targetMode;
@@ -60,4 +66,19 @@
         }
         return false;
     }
+
+    public void addDependentTransactionApplier(SyncRtSurfaceTransactionApplierCompat delay) {
+        mDependentTransactionAppliers.add(delay);
+    }
+
+    public void release() {
+        SyncRtSurfaceTransactionApplierCompat applier = mDependentTransactionAppliers.poll();
+        if (applier == null) {
+            for (RemoteAnimationTargetCompat target : unfilteredApps) {
+                target.release();
+            }
+        } else {
+            applier.addAfterApplyCallback(this::release);
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index e552f56..93e403c 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -71,7 +71,8 @@
 
     @Override
     public Statement apply(Statement base, Description description) {
-        if (TestHelpers.isInLauncherProcess() &&
+        // b/130558787; b/131419978
+        if (false && TestHelpers.isInLauncherProcess() &&
                 description.getAnnotation(NavigationModeSwitch.class) != null) {
             Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
             return new Statement() {
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index e6c2d0c..90e673b 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -8,13 +8,10 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.WindowInsets;
@@ -32,9 +29,6 @@
     private final Rect mConsumedInsets = new Rect();
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private final RectF mTouchExcludeRegion = new RectF();
-
-    @ViewDebug.ExportedProperty(category = "launcher")
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
 
@@ -164,30 +158,11 @@
 
     @Override
     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
-        if (Utilities.ATLEAST_Q) {
-            Insets gestureInsets = insets.getMandatorySystemGestureInsets();
-            mTouchExcludeRegion.set(gestureInsets.left, gestureInsets.top,
-                    gestureInsets.right, gestureInsets.bottom);
-        }
+        mLauncher.getDragLayer().updateTouchExcludeRegion(insets);
         return super.dispatchApplyWindowInsets(insets);
     }
 
     @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            float x = ev.getX();
-            float y = ev.getY();
-            if (y < mTouchExcludeRegion.top
-                    || x < mTouchExcludeRegion.left
-                    || x > (getWidth() - mTouchExcludeRegion.right)
-                    || y > (getHeight() - mTouchExcludeRegion.bottom)) {
-                return false;
-            }
-        }
-        return super.dispatchTouchEvent(ev);
-    }
-
-    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(l, t, r, b);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 593dbd4..c7d93fe 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -173,8 +172,7 @@
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             BaseDragLayer dl = getPopupContainer();
-            final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
-            if (!cameFromNavBar && !dl.isEventOverView(this, ev)) {
+            if (!dl.isEventOverView(this, ev)) {
                 mLauncher.getUserEventDispatcher().logActionTapOutside(
                         LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
                 close(true);
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 66cd536..8a15220 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -22,13 +22,19 @@
 
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 
+import android.annotation.TargetApi;
 import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewDebug;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 
@@ -74,18 +80,32 @@
                 }
             };
 
+    // Touch is being dispatched through the normal view dispatch system
+    private static final int TOUCH_DISPATCHING_VIEW = 1 << 0;
+    // Touch is being dispatched through the normal view dispatch system, and started at the
+    // system gesture region
+    private static final int TOUCH_DISPATCHING_GESTURE = 1 << 1;
+    // Touch is being dispatched through a proxy from InputMonitor
+    private static final int TOUCH_DISPATCHING_PROXY = 1 << 2;
+
     protected final int[] mTmpXY = new int[2];
     protected final Rect mHitRect = new Rect();
 
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private final RectF mSystemGestureRegion = new RectF();
+    private int mTouchDispatchState = 0;
+
     protected final T mActivity;
     private final MultiValueAlpha mMultiValueAlpha;
 
+    // All the touch controllers for the view
     protected TouchController[] mControllers;
+    // Touch controller which is currently active for the normal view dispatch
     protected TouchController mActiveController;
-    private TouchCompleteListener mTouchCompleteListener;
+    // Touch controller which is being used for the proxy events
+    protected TouchController mProxyTouchController;
 
-    // Object controlling the current touch interaction
-    private Object mCurrentTouchOwner;
+    private TouchCompleteListener mTouchCompleteListener;
 
     public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
         super(context, attrs);
@@ -113,30 +133,36 @@
         return findActiveController(ev);
     }
 
+    private TouchController findControllerToHandleTouch(MotionEvent ev) {
+        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+        if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
+            return topView;
+        }
+
+        for (TouchController controller : mControllers) {
+            if (controller.onControllerInterceptTouchEvent(ev)) {
+                return controller;
+            }
+        }
+        return null;
+    }
+
     protected boolean findActiveController(MotionEvent ev) {
         if (com.android.launcher3.TestProtocol.sDebugTracing) {
             android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
                     "mActiveController = null");
         }
         mActiveController = null;
+        if ((mTouchDispatchState & (TOUCH_DISPATCHING_GESTURE | TOUCH_DISPATCHING_PROXY)) == 0) {
+            // Only look for controllers if we are not dispatching from gesture area and proxy is
+            // not active
+            mActiveController = findControllerToHandleTouch(ev);
 
-        AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
-        if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
-            if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                        "setting controller1: " + topView.getClass().getSimpleName());
-            }
-            mActiveController = topView;
-            return true;
-        }
-
-        for (TouchController controller : mControllers) {
-            if (controller.onControllerInterceptTouchEvent(ev)) {
+            if (mActiveController != null) {
                 if (com.android.launcher3.TestProtocol.sDebugTracing) {
                     android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                            "setting controller1: " + controller.getClass().getSimpleName());
+                            "setting controller1: " + mActiveController.getClass().getSimpleName());
                 }
-                mActiveController = controller;
                 return true;
             }
         }
@@ -223,40 +249,75 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        return dispatchTouchEvent(this, ev);
-    }
+        switch (ev.getAction()) {
+            case ACTION_DOWN: {
+                float x = ev.getX();
+                float y = ev.getY();
+                mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
 
-    public boolean dispatchTouchEvent(Object caller, MotionEvent ev) {
-        return verifyTouchDispatch(caller, ev) && super.dispatchTouchEvent(ev);
+                if ((y < mSystemGestureRegion.top
+                        || x < mSystemGestureRegion.left
+                        || x > (getWidth() - mSystemGestureRegion.right)
+                        || y > (getHeight() - mSystemGestureRegion.bottom))) {
+                    mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
+                } else {
+                    mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+                }
+                break;
+            }
+            case ACTION_CANCEL:
+            case ACTION_UP:
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
+                break;
+        }
+        super.dispatchTouchEvent(ev);
+
+        // We want to get all events so that mTouchDispatchSource is maintained properly
+        return true;
     }
 
     /**
-     * Returns true if the {@param caller} is allowed to dispatch {@param ev} on this view,
-     * false otherwise.
+     * Called before we are about to receive proxy events.
+     *
+     * @return false if we can't handle proxy at this time
      */
-    private boolean verifyTouchDispatch(Object caller, MotionEvent ev) {
-        int action = ev.getAction();
-        if (action == ACTION_DOWN) {
-            if (mCurrentTouchOwner != null) {
-                // Another touch in progress.
-                ev.setAction(ACTION_CANCEL);
-                super.dispatchTouchEvent(ev);
-                ev.setAction(action);
-            }
-            mCurrentTouchOwner = caller;
-            return true;
-        }
-        if (mCurrentTouchOwner != caller) {
-            // Someone else is controlling the touch
+    public boolean prepareProxyEventStarting() {
+        mProxyTouchController = null;
+        if ((mTouchDispatchState & TOUCH_DISPATCHING_VIEW) != 0 && mActiveController != null) {
+            // We are already dispatching using view system and have an active controller, we can't
+            // handle another controller.
+
+            // This flag was already cleared in proxy ACTION_UP or ACTION_CANCEL. Added here just
+            // to be safe
+            mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
             return false;
         }
-        if (action == ACTION_UP || action == ACTION_CANCEL) {
-            mCurrentTouchOwner = null;
-        }
+
+        mTouchDispatchState |= TOUCH_DISPATCHING_PROXY;
         return true;
     }
 
     /**
+     * Proxies the touch events to the gesture handlers
+     */
+    public boolean proxyTouchEvent(MotionEvent ev) {
+        boolean handled;
+        if (mProxyTouchController != null) {
+            handled = mProxyTouchController.onControllerTouchEvent(ev);
+        } else {
+            mProxyTouchController = findControllerToHandleTouch(ev);
+            handled = mProxyTouchController != null;
+        }
+        int action = ev.getAction();
+        if (action == ACTION_UP || action == ACTION_CANCEL) {
+            mProxyTouchController = null;
+            mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
+        }
+        return handled;
+    }
+
+    /**
      * Determine the rect of the descendant in this DragLayer's coordinates
      *
      * @param descendant The descendant whose coordinates we want to find.
@@ -423,4 +484,13 @@
             }
         }
     }
+
+    @TargetApi(Build.VERSION_CODES.Q)
+    public void updateTouchExcludeRegion(WindowInsets insets) {
+        if (Utilities.ATLEAST_Q) {
+            Insets gestureInsets = insets.getMandatorySystemGestureInsets();
+            mSystemGestureRegion.set(gestureInsets.left, gestureInsets.top,
+                    gestureInsets.right, gestureInsets.bottom);
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index a37218b..3e84440 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,6 +17,10 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY;
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -29,6 +33,7 @@
 import android.content.IntentFilter;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
@@ -57,6 +62,7 @@
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.TestRule;
@@ -67,6 +73,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Method;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -102,6 +109,68 @@
         }
         if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
         mLauncher = new LauncherInstrumentation(instrumentation);
+
+        // b/130558787; b/131419978
+        if (TestHelpers.isInLauncherProcess()) {
+            try {
+                Class systemProps = Class.forName("android.os.SystemProperties");
+                Method getInt = systemProps.getMethod("getInt", String.class, int.class);
+                int apiLevel = (int) getInt.invoke(null, "ro.product.first_api_level", 0);
+
+                if (apiLevel >= Build.VERSION_CODES.P) {
+                    setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.TWO_BUTTON);
+                }
+                if (apiLevel >= Build.VERSION_CODES.O && apiLevel < Build.VERSION_CODES.P) {
+                    setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON);
+                }
+                if (apiLevel < Build.VERSION_CODES.O) {
+                    setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.THREE_BUTTON);
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void setActiveOverlay(String overlayPackage,
+            LauncherInstrumentation.NavigationModel expectedMode) {
+        setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
+                overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
+        setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
+                overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
+        setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+
+        for (int i = 0; i != 100; ++i) {
+            if (mLauncher.getNavigationModel() == expectedMode) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                return;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        Assert.fail("Couldn't switch to " + overlayPackage);
+    }
+
+    private void setOverlayPackageEnabled(String overlayPackage, boolean enable) {
+        Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
+        final String action = enable ? "enable" : "disable";
+        try {
+            UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+                    "cmd overlay " + action + " " + overlayPackage);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 
     @Rule
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index c55bc72..2a69757 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -239,12 +239,6 @@
         // Test starting a workspace app.
         final AppIcon app = workspace.tryGetWorkspaceAppIcon("Chrome");
         assertNotNull("No Chrome app in workspace", app);
-        assertNotNull("AppIcon.launch returned null",
-                app.launch(resolveSystemApp(Intent.CATEGORY_APP_BROWSER)));
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInBackground(launcher)));
     }
 
     public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
@@ -340,6 +334,10 @@
                     dragToWorkspace().
                     getWorkspaceAppIcon(APP_NAME).
                     launch(getAppPackageName());
+            executeOnLauncher(launcher -> assertTrue(
+                    "Launcher activity is the top activity; expecting another activity to be the "
+                            + "top one",
+                    isInBackground(launcher)));
         } finally {
             TestProtocol.sDebugTracing = false;
         }