Merge "Enabling quick-switch gesture in landscape mode (2 button UI)" into ub-launcher3-qt-dev
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/go/quickstep/res/layout/clear_all_button.xml b/go/quickstep/res/layout/clear_all_button.xml
index be76d53..2f7c8ae 100644
--- a/go/quickstep/res/layout/clear_all_button.xml
+++ b/go/quickstep/res/layout/clear_all_button.xml
@@ -14,22 +14,20 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.quickstep.views.ClearAllItemView
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/clear_all_item_view"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="@dimen/clear_all_item_view_height">
     <Button
         android:id="@+id/clear_all_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginVertical="16dp"
+        android:layout_width="@dimen/clear_all_button_width"
+        android:layout_height="match_parent"
         android:layout_gravity="center_horizontal"
-        android:paddingHorizontal="32dp"
         android:background="@drawable/clear_all_button"
         android:gravity="center"
         android:text="@string/recents_clear_all"
         android:textAllCaps="false"
         android:textColor="@color/clear_all_button_text"
         android:textSize="14sp"/>
-</com.android.quickstep.views.ClearAllItemView>
+</FrameLayout>
diff --git a/go/quickstep/res/layout/icon_recents_root_view.xml b/go/quickstep/res/layout/icon_recents_root_view.xml
index 6fb7e19..b64b7fd 100644
--- a/go/quickstep/res/layout/icon_recents_root_view.xml
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -21,9 +21,11 @@
     android:orientation="vertical">
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/recent_task_recycler_view"
-        android:layout_width="match_parent"
+        android:layout_width="@dimen/recents_list_width"
         android:layout_height="match_parent"
-        android:scrollbars="none"/>
+        android:layout_gravity="center_horizontal"
+        android:scrollbars="none"
+        android:clipToPadding="false"/>
     <TextView
         android:id="@+id/recent_task_empty_view"
         android:layout_width="match_parent"
diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml
index 1483d4c..699178d 100644
--- a/go/quickstep/res/layout/task_item_view.xml
+++ b/go/quickstep/res/layout/task_item_view.xml
@@ -17,14 +17,13 @@
 <com.android.quickstep.views.TaskItemView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_height="@dimen/task_item_height"
     android:orientation="horizontal">
     <com.android.quickstep.views.TaskThumbnailIconView
         android:id="@+id/task_icon_and_thumbnail"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginHorizontal="8dp"
-        android:layout_marginTop="16dp">
+        android:layout_marginHorizontal="@dimen/task_thumbnail_icon_horiz_margin">
         <ImageView
             android:id="@+id/task_thumbnail"
             android:layout_width="match_parent"
@@ -39,7 +38,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginHorizontal="8dp"
         android:singleLine="true"
         android:textColor="@android:color/white"
         android:textSize="24sp"/>
diff --git a/go/quickstep/res/values-sw480dp/dimens.xml b/go/quickstep/res/values-sw480dp/dimens.xml
new file mode 100644
index 0000000..b48dafb
--- /dev/null
+++ b/go/quickstep/res/values-sw480dp/dimens.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<resources>
+    <dimen name="recents_list_width">480dp</dimen>
+
+    <dimen name="task_item_height">90dp</dimen>
+    <dimen name="task_item_top_margin">16dp</dimen>
+    <dimen name="task_thumbnail_icon_horiz_margin">20dp</dimen>
+
+    <dimen name="task_thumbnail_corner_radius">4dp</dimen>
+
+    <dimen name="clear_all_item_view_height">48dp</dimen>
+    <dimen name="clear_all_item_view_top_margin">28dp</dimen>
+    <dimen name="clear_all_item_view_bottom_margin">28dp</dimen>
+    <dimen name="clear_all_button_width">140dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
index ee154fc..91040f2 100644
--- a/go/quickstep/res/values/dimens.xml
+++ b/go/quickstep/res/values/dimens.xml
@@ -15,5 +15,16 @@
      limitations under the License.
 -->
 <resources>
+    <dimen name="recents_list_width">320dp</dimen>
+
+    <dimen name="task_item_height">60dp</dimen>
+    <dimen name="task_item_top_margin">16dp</dimen>
+    <dimen name="task_thumbnail_icon_horiz_margin">16dp</dimen>
+
     <dimen name="task_thumbnail_corner_radius">3dp</dimen>
+
+    <dimen name="clear_all_item_view_height">36dp</dimen>
+    <dimen name="clear_all_item_view_top_margin">20dp</dimen>
+    <dimen name="clear_all_item_view_bottom_margin">20dp</dimen>
+    <dimen name="clear_all_button_width">106dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/go/quickstep/src/com/android/quickstep/views/ClearAllItemView.java b/go/quickstep/src/com/android/quickstep/views/ClearAllItemView.java
deleted file mode 100644
index 378dbf4..0000000
--- a/go/quickstep/src/com/android/quickstep/views/ClearAllItemView.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.views;
-
-import static com.android.quickstep.views.TaskLayoutUtils.getClearAllItemHeight;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-/**
- * Recycler view item that lays out the clear all button and measures the space it takes based on
- * the device height.
- */
-public final class ClearAllItemView extends FrameLayout {
-
-    public ClearAllItemView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int buttonHeight = getClearAllItemHeight(getContext());
-        int newHeightSpec = MeasureSpec.makeMeasureSpec(buttonHeight, MeasureSpec.EXACTLY);
-        super.onMeasure(widthMeasureSpec, newHeightSpec);
-    }
-}
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index cf6eb6d..f951304 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -20,6 +20,8 @@
 import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
 
 import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
+import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
+import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK;
 import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
 
 import android.animation.Animator;
@@ -29,6 +31,8 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -44,9 +48,11 @@
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration;
 import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
 
 import com.android.launcher3.BaseActivity;
+import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.quickstep.ContentFillItemAnimator;
 import com.android.quickstep.RecentsModel;
@@ -67,7 +73,7 @@
  * Root view for the icon recents view. Acts as the main interface to the rest of the Launcher code
  * base.
  */
-public final class IconRecentsView extends FrameLayout {
+public final class IconRecentsView extends FrameLayout implements Insettable {
 
     public static final FloatProperty<IconRecentsView> CONTENT_ALPHA =
             new FloatProperty<IconRecentsView>("contentAlpha") {
@@ -117,6 +123,7 @@
     private boolean mTransitionedFromApp;
     private AnimatorSet mLayoutAnimation;
     private final ArraySet<View> mLayingOutViews = new ArraySet<>();
+    private Rect mInsets;
     private final RecentsModel.TaskThumbnailChangeListener listener = (taskId, thumbnailData) -> {
         ArrayList<TaskItemView> itemViews = getTaskViews();
         for (int i = 0, size = itemViews.size(); i < size; i++) {
@@ -174,6 +181,43 @@
             mTaskRecyclerView.setItemAnimator(mDefaultItemAnimator);
             mLoadingContentItemAnimator.setOnAnimationFinishedRunnable(
                     () -> mTaskRecyclerView.setItemAnimator(new DefaultItemAnimator()));
+            ItemDecoration marginDecorator = new ItemDecoration() {
+                @Override
+                public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
+                        @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+                    // TODO: Determine if current margins cause off screen item to be fully off
+                    // screen and if so, modify them so that it is partially off screen.
+                    int itemType = parent.getChildViewHolder(view).getItemViewType();
+                    Resources res = getResources();
+                    switch (itemType) {
+                        case ITEM_TYPE_CLEAR_ALL:
+                            outRect.top = (int) res.getDimension(
+                                    R.dimen.clear_all_item_view_top_margin);
+                            int desiredBottomMargin = (int) res.getDimension(
+                                    R.dimen.clear_all_item_view_bottom_margin);
+                            // Only add bottom margin if insets aren't enough.
+                            if (mInsets.bottom < desiredBottomMargin) {
+                                outRect.bottom = desiredBottomMargin - mInsets.bottom;
+                            }
+                            break;
+                        case ITEM_TYPE_TASK:
+                            int desiredTopMargin = (int) res.getDimension(
+                                    R.dimen.task_item_top_margin);
+                            if (mTaskRecyclerView.getChildAdapterPosition(view) ==
+                                    state.getItemCount() - 1) {
+                                // Only add top margin to top task view if insets aren't enough.
+                                if (mInsets.top < desiredTopMargin) {
+                                    outRect.top = desiredTopMargin - mInsets.bottom;
+                                }
+                                return;
+                            }
+                            outRect.top = desiredTopMargin;
+                            break;
+                        default:
+                    }
+                }
+            };
+            mTaskRecyclerView.addItemDecoration(marginDecorator);
 
             mEmptyView = findViewById(R.id.recent_task_empty_view);
             mContentView = mTaskRecyclerView;
@@ -188,7 +232,6 @@
                     updateContentViewVisibility();
                 }
             });
-            // TODO: Move layout param logic into onMeasure
         }
     }
 
@@ -482,4 +525,11 @@
         });
         mLayoutAnimation.start();
     }
+
+    @Override
+    public void setInsets(Rect insets) {
+        mInsets = insets;
+        mTaskRecyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+        mTaskRecyclerView.invalidateItemDecorations();
+    }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
index 9019205..a5f5728 100644
--- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -15,8 +15,6 @@
  */
 package com.android.quickstep.views;
 
-import static com.android.quickstep.views.TaskLayoutUtils.getTaskHeight;
-
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -46,6 +44,7 @@
     private final Drawable mDefaultThumbnail;
     private final TaskLayerDrawable mIconDrawable;
     private final TaskLayerDrawable mThumbnailDrawable;
+    private View mTaskIconThumbnailView;
     private TextView mLabelView;
     private ImageView mIconView;
     private ImageView mThumbnailView;
@@ -81,6 +80,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mLabelView = findViewById(R.id.task_label);
+        mTaskIconThumbnailView = findViewById(R.id.task_icon_and_thumbnail);
         mThumbnailView = findViewById(R.id.task_thumbnail);
         mIconView = findViewById(R.id.task_icon);
 
@@ -91,13 +91,6 @@
         CONTENT_TRANSITION_PROGRESS.setValue(this, 1.0f);
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int taskHeight = getTaskHeight(getContext());
-        int newHeightSpec = MeasureSpec.makeMeasureSpec(taskHeight,MeasureSpec.EXACTLY);
-        super.onMeasure(widthMeasureSpec, newHeightSpec);
-    }
-
     /**
      * Resets task item view to empty, loading UI.
      */
@@ -195,5 +188,6 @@
                 ((ThumbnailDrawable) drawable).setRequestedOrientation(newConfig.orientation);
             }
         }
+        mTaskIconThumbnailView.forceLayout();
     }
 }
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskLayoutUtils.java b/go/quickstep/src/com/android/quickstep/views/TaskLayoutUtils.java
deleted file mode 100644
index e28a9e0..0000000
--- a/go/quickstep/src/com/android/quickstep/views/TaskLayoutUtils.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.views;
-
-import static com.android.quickstep.TaskAdapter.MAX_TASKS_TO_DISPLAY;
-
-import android.content.Context;
-
-import com.android.launcher3.InvariantDeviceProfile;
-
-/**
- * Utils to determine dynamically task and view sizes based off the device height and width.
- */
-public final class TaskLayoutUtils {
-
-    private static final float CLEAR_ALL_ITEM_TO_HEIGHT_RATIO = 7.0f / 64;
-
-    private TaskLayoutUtils() {}
-
-    /**
-     * Calculate task height based off the available height in portrait mode such that when the
-     * recents list is full, the total height fills in the available device height perfectly. In
-     * landscape mode, we keep the same task height so that tasks scroll off the top.
-     *
-     * @param context current context
-     * @return task height
-     */
-    public static int getTaskHeight(Context context) {
-        final int availableHeight =
-                InvariantDeviceProfile.INSTANCE.get(context).portraitProfile.availableHeightPx;
-        final int availableTaskSpace = availableHeight - getClearAllItemHeight(context);
-        return (int) (availableTaskSpace * 1.0f / MAX_TASKS_TO_DISPLAY);
-    }
-
-    /**
-     * Calculate clear all item height scaled to available height in portrait mode.
-     *
-     * @param context current context
-     * @return clear all item height
-     */
-    public static int getClearAllItemHeight(Context context) {
-        final int availableHeight =
-                InvariantDeviceProfile.INSTANCE.get(context).portraitProfile.availableHeightPx;
-        return (int) (CLEAR_ALL_ITEM_TO_HEIGHT_RATIO * availableHeight);
-    }
-}
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java b/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java
index 0bad77b..eaefa21 100644
--- a/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java
+++ b/go/quickstep/src/com/android/quickstep/views/TaskThumbnailIconView.java
@@ -56,7 +56,6 @@
         int width = height;
         setMeasuredDimension(width, height);
 
-
         int subItemSize = (int) (SUBITEM_FRAME_RATIO * height);
         if (mThumbnailView.getVisibility() != GONE) {
             boolean isPortrait =
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/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index e5747dc..624b3dc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -137,7 +137,7 @@
         Rect targetRect = new Rect();
         mHelper.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity, targetRect);
         clipHelper.updateTargetRect(targetRect);
-        clipHelper.prepareAnimation(false /* isOpening */);
+        clipHelper.prepareAnimation(mActivity.getDeviceProfile(), false /* isOpening */);
 
         ClipAnimationHelper.TransformParams params = new ClipAnimationHelper.TransformParams()
                 .setSyncTransactionApplier(new SyncRtSurfaceTransactionApplierCompat(rootView));
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 90af521..833a468 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
@@ -334,18 +334,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.
@@ -376,7 +379,7 @@
             mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
             mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
             if (mSwipeSharedState.canGestureBeContinued) {
-                mInteractionHandler.cancel();
+                mInteractionHandler.cancelCurrentAnimation();
             } else {
                 mInteractionHandler.reset();
             }
@@ -414,4 +417,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/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..f95f9c2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -19,13 +19,18 @@
 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;
 import android.view.View;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.quickstep.util.ClipAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
@@ -109,8 +114,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 +131,17 @@
             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.prepareAnimation(
+                        BaseActivity.fromContext(v.getContext()).getDeviceProfile(),
+                        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 +151,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 +162,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 d7d0c36..dfcc2b7 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 c94ad69..2471f64 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(
@@ -729,7 +730,7 @@
         if (runningTaskTarget != null) {
             mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
         }
-        mClipAnimationHelper.prepareAnimation(false /* isOpening */);
+        mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
         initTransitionEndpoints(dp);
 
         mRecentsAnimationWrapper.setController(targetSet);
@@ -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
@@ -806,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;
@@ -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 */);
 
@@ -1062,7 +1076,9 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 homeAnim.dispatchOnStart();
-                mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+                if (mActivity != null) {
+                    mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+                }
             }
 
             @Override
@@ -1106,7 +1122,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/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 1242d79..cb2bf1f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -81,6 +81,7 @@
     private final RectF mClipRectF = new RectF();
     private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
     private final Matrix mTmpMatrix = new Matrix();
+    private final Rect mTmpRect = new Rect();
     private final RectF mTmpRectF = new RectF();
     private final RectF mCurrentRectWithInsets = new RectF();
     // Corner radius of windows, in pixels
@@ -89,6 +90,8 @@
     private final float mTaskCornerRadius;
     // If windows can have real time rounded corners.
     private final boolean mSupportsRoundedCornersOnWindows;
+    // Whether or not to actually use the rounded cornders on windows
+    private boolean mUseRoundedCornersOnWindows;
 
     // Corner radius currently applied to transformed window.
     private float mCurrentCornerRadius;
@@ -103,6 +106,7 @@
         mWindowCornerRadius = getWindowCornerRadius(context.getResources());
         mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
         mTaskCornerRadius = TaskCornerRadius.get(context);
+        mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows;
     }
 
     private void updateSourceStack(RemoteAnimationTargetCompat target) {
@@ -144,8 +148,9 @@
         mSourceRect.set(scaledTargetRect);
     }
 
-    public void prepareAnimation(boolean isOpening) {
+    public void prepareAnimation(DeviceProfile dp, boolean isOpening) {
         mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
+        mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode;
     }
 
     public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params) {
@@ -177,7 +182,9 @@
         for (int i = 0; i < targetSet.unfilteredApps.length; i++) {
             RemoteAnimationTargetCompat app = targetSet.unfilteredApps[i];
             mTmpMatrix.setTranslate(app.position.x, app.position.y);
-            Rect crop = app.sourceContainerBounds;
+            Rect crop = mTmpRect;
+            crop.set(app.sourceContainerBounds);
+            crop.offsetTo(0, 0);
             float alpha = 1f;
             int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers);
             float cornerRadius = 0f;
@@ -188,7 +195,9 @@
                     mTmpMatrix.postTranslate(app.position.x, app.position.y);
                     mClipRectF.roundOut(crop);
                     if (mSupportsRoundedCornersOnWindows) {
-                        cornerRadius = Utilities.mapRange(params.progress, mWindowCornerRadius,
+                        float windowCornerRadius = mUseRoundedCornersOnWindows
+                                ? mWindowCornerRadius : 0;
+                        cornerRadius = Utilities.mapRange(params.progress, windowCornerRadius,
                                 mTaskCornerRadius);
                         mCurrentCornerRadius = cornerRadius;
                     }
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/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index cda9d4f..a8666f9 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -416,17 +416,15 @@
 
         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.
-        float maxScaleX = windowTargetBounds.width() / (float) bounds.width();
-        // We use windowTargetBounds.width for scaleY too since we start off the animation where the
-        // window is clipped to a square.
-        float maxScaleY = windowTargetBounds.width() / (float) bounds.height();
+        float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width());
+        float maxScaleX = smallestSize / (float) bounds.width();
+        float maxScaleY = smallestSize / (float) bounds.height();
         float scale = Math.max(maxScaleX, maxScaleY);
         float startScale = 1f;
         if (v instanceof BubbleTextView && !(v.getParent() instanceof DeepShortcutView)) {
@@ -470,6 +468,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/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
index 43f6039..72de80b 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -37,6 +37,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -49,7 +50,6 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class AppPredictionsUITests  extends AbstractQuickStepTest {
-    private static final int DEFAULT_APP_LAUNCH_TIMES = 3;
     private static final String TAG = "AppPredictionsUITests";
 
     private LauncherActivityInfo mSampleApp1;
@@ -86,6 +86,7 @@
      * Test that prediction UI is updated as soon as we get predictions from the system
      */
     @Test
+    @Ignore // b/131188880
     public void testPredictionExistsInAllApps() {
         mActivityMonitor.startLauncher();
         mLauncher.pressHome().switchToAllApps();
@@ -106,6 +107,7 @@
      * Test tat prediction update is deferred if it is already visible
      */
     @Test
+    @Ignore // b/131188880
     public void testPredictionsDeferredUntilHome() {
         mActivityMonitor.startLauncher();
         sendPredictionUpdate(mSampleApp1, mSampleApp2);
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 20fdff2..960d907 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.testcomponent.TestCommandReceiver;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -97,6 +98,7 @@
 
     @NavigationModeSwitch(mode = THREE_BUTTON)
     @Test
+    @Ignore // b/131630813
     public void goToOverviewFromHome() {
         mDevice.pressHome();
         assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
@@ -107,6 +109,7 @@
 
     @NavigationModeSwitch(mode = THREE_BUTTON)
     @Test
+    @Ignore // b/131630813
     public void goToOverviewFromApp() {
         startAppFast("com.android.settings");
 
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index f2fc718..77f278a 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -78,6 +78,7 @@
 
     private final int mBlurSizeOutline;
 
+    private boolean mIsVerticalBarLayout = false;
     private boolean mIsAdaptiveIcon = false;
 
     private @Nullable Drawable mForeground;
@@ -273,7 +274,7 @@
                 }
 
                 float aspectRatio = launcher.getDeviceProfile().aspectRatio;
-                if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+                if (mIsVerticalBarLayout) {
                     lp.width = (int) Math.max(lp.width, lp.height * aspectRatio);
                 } else {
                     lp.height = (int) Math.max(lp.height, lp.width * aspectRatio);
@@ -318,8 +319,13 @@
         mBgDrawableBounds.set(mFinalDrawableBounds);
         Utilities.scaleRectAboutCenter(mBgDrawableBounds, scale);
         // Since the drawable is at the top of the view, we need to offset to keep it centered.
-        mBgDrawableBounds.offsetTo(mBgDrawableBounds.left,
-                (int) (mFinalDrawableBounds.top * scale));
+        if (mIsVerticalBarLayout) {
+            mBgDrawableBounds.offsetTo((int) (mFinalDrawableBounds.left  * scale),
+                    mBgDrawableBounds.top);
+        } else {
+            mBgDrawableBounds.offsetTo(mBgDrawableBounds.left,
+                    (int) (mFinalDrawableBounds.top * scale));
+        }
         mBackground.setBounds(mBgDrawableBounds);
     }
 
@@ -410,6 +416,7 @@
             recycle.recycle();
         }
         FloatingIconView view = recycle != null ? recycle : new FloatingIconView(launcher);
+        view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout();
 
         // Match the position of the original view.
         view.matchPositionOf(launcher, originalView, positionOut);
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 3e84440..dedc6b3 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -111,7 +111,7 @@
         mLauncher = new LauncherInstrumentation(instrumentation);
 
         // b/130558787; b/131419978
-        if (TestHelpers.isInLauncherProcess()) {
+        if (TestHelpers.isInLauncherProcess() && !LauncherInstrumentation.needSlowGestures()) {
             try {
                 Class systemProps = Class.forName("android.os.SystemProperties");
                 Method getInt = systemProps.getMethod("getInt", String.class, int.class);
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 74a17ce..70d8cf7 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -28,7 +28,7 @@
  * Common overview pane for both Launcher and fallback recents
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
-    private static final int FLING_SPEED = 1500;
+    private static final int FLING_SPEED = LauncherInstrumentation.needSlowGestures() ? 500 : 1500;
     private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
 
     BaseOverview(LauncherInstrumentation launcher) {
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 27bc43e..f5c5a8d 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -191,7 +191,7 @@
         return NavigationModel.THREE_BUTTON;
     }
 
-    static boolean needSlowGestures() {
+    public static boolean needSlowGestures() {
         return Build.MODEL.contains("Cuttlefish");
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 0b3bbd2..85e112a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -36,7 +36,8 @@
  * Operations on the workspace screen.
  */
 public final class Workspace extends Home {
-    private static final float FLING_SPEED = 3500.0F;
+    private static final float FLING_SPEED =
+            LauncherInstrumentation.needSlowGestures() ? 1500.0F : 3500.0F;
     private static final int DRAG_DURACTION = 2000;
     private final UiObject2 mHotseat;