Merge "Adding support for accessibiliy gesture" into ub-launcher3-qt-dev
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/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 32e0e48..b48e3de 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
@@ -65,6 +65,11 @@
     }
 
     @Override
+    public boolean allowInterceptByParent() {
+        return !mTargetHandledTouch;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         if (!mProxyTouch) {
             return;
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 b62bac6..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, mInputMonitorCompat, false);
+            base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
                 activityControl.isInLiveTileMode()) {
-            return OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, 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 50a60fd..3797e87 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -770,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.
@@ -789,7 +800,7 @@
             mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
         }
         mDownPos = downPos;
-        handleNormalGestureEnd(endVelocity, isFling, velocity);
+        handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
     @UiThread
@@ -807,7 +818,8 @@
     }
 
     @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;
@@ -825,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) {
@@ -908,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;
         }
@@ -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()) {