Merging  ub-launcher3-qt-dev, build 5528135
am: 4c8e5dd275

Change-Id: Ic9ece52afbf3b61453a17bc4f85f79093622852c
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..595a380 100644
--- a/go/quickstep/res/layout/icon_recents_root_view.xml
+++ b/go/quickstep/res/layout/icon_recents_root_view.xml
@@ -18,12 +18,16 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:clipChildren="false">
     <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"
+        android:clipChildren="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/launcher3/uioverrides/states/OverviewState.java b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d20910f..1b24fc8 100644
--- a/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/go/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -52,12 +52,19 @@
     public void onStateEnabled(Launcher launcher) {
         IconRecentsView recentsView = launcher.getOverviewPanel();
         recentsView.onBeginTransitionToOverview();
+        recentsView.setShowStatusBarForegroundScrim(true);
         // Request orientation be set to unspecified, letting the system decide the best
         // orientation.
         launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
     }
 
     @Override
+    public void onStateDisabled(Launcher launcher) {
+        IconRecentsView recentsView = launcher.getOverviewPanel();
+        recentsView.setShowStatusBarForegroundScrim(false);
+    }
+
+    @Override
     public PageAlphaProvider getWorkspacePageAlphaProvider(Launcher launcher) {
         return new PageAlphaProvider(DEACCEL_2) {
             @Override
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
index f2ca368..9fb8067 100644
--- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -37,6 +37,7 @@
         mRecentsRootView = findViewById(R.id.drag_layer);
         mIconRecentsView = findViewById(R.id.overview_panel);
         mIconRecentsView.setRecentsToActivityHelper(new FallbackRecentsToActivityHelper(this));
+        mIconRecentsView.setShowStatusBarForegroundScrim(true);
     }
 
     @Override
diff --git a/go/quickstep/src/com/android/quickstep/TaskListLoader.java b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
index 850c7e6..1335cac 100644
--- a/go/quickstep/src/com/android/quickstep/TaskListLoader.java
+++ b/go/quickstep/src/com/android/quickstep/TaskListLoader.java
@@ -80,7 +80,8 @@
             return;
         }
         // TODO: Look into error checking / more robust handling for when things go wrong.
-        mTaskListChangeId = mRecentsModel.getTasks(tasks -> {
+        mTaskListChangeId = mRecentsModel.getTasks(loadedTasks -> {
+            ArrayList<Task> tasks = new ArrayList<>(loadedTasks);
             // Reverse tasks to put most recent at the bottom of the view
             Collections.reverse(tasks);
             // Load task content
diff --git a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
index 19951bb..57f49d6 100644
--- a/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
+++ b/go/quickstep/src/com/android/quickstep/TaskSwipeCallback.java
@@ -19,21 +19,25 @@
 
 import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
 
+import android.graphics.Canvas;
+
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.ItemTouchHelper;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
+import java.util.function.Consumer;
+
 /**
  * Callback for swipe input on {@link TaskHolder} views in the recents view.
  */
 public final class TaskSwipeCallback extends ItemTouchHelper.SimpleCallback {
 
-    private final TaskActionController mTaskActionController;
+    private final Consumer<TaskHolder> mOnTaskSwipeCallback;
 
-    public TaskSwipeCallback(TaskActionController taskActionController) {
+    public TaskSwipeCallback(Consumer<TaskHolder> onTaskSwipeCallback) {
         super(0 /* dragDirs */, RIGHT);
-        mTaskActionController = taskActionController;
+        mOnTaskSwipeCallback = onTaskSwipeCallback;
     }
 
     @Override
@@ -45,11 +49,23 @@
     @Override
     public void onSwiped(ViewHolder viewHolder, int direction) {
         if (direction == RIGHT) {
-            mTaskActionController.removeTask((TaskHolder) viewHolder);
+            mOnTaskSwipeCallback.accept((TaskHolder) viewHolder);
         }
     }
 
     @Override
+    public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView,
+            @NonNull ViewHolder viewHolder, float dX, float dY, int actionState,
+            boolean isCurrentlyActive) {
+        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
+            float alpha = 1.0f - dX / (float) viewHolder.itemView.getWidth();
+            viewHolder.itemView.setAlpha(alpha);
+        }
+        super.onChildDraw(c, recyclerView, viewHolder, dX, dY,
+                    actionState, isCurrentlyActive);
+    }
+
+    @Override
     public int getSwipeDirs(@NonNull RecyclerView recyclerView,
             @NonNull ViewHolder viewHolder) {
         if (viewHolder.getItemViewType() == ITEM_TYPE_CLEAR_ALL) {
diff --git a/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java b/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java
index a8cc0a1..922e68a 100644
--- a/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java
+++ b/go/quickstep/src/com/android/quickstep/ThumbnailDrawable.java
@@ -57,6 +57,7 @@
         mCornerRadius = (int) res.getDimension(R.dimen.task_thumbnail_corner_radius);
         mShader = new BitmapShader(mThumbnailData.thumbnail, CLAMP, CLAMP);
         mPaint.setShader(mShader);
+        mPaint.setAntiAlias(true);
         updateMatrix();
     }
 
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..7225e57 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,9 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -44,10 +49,13 @@
 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.launcher3.util.Themes;
 import com.android.quickstep.ContentFillItemAnimator;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsToActivityHelper;
@@ -67,7 +75,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") {
@@ -108,6 +116,8 @@
     private final DefaultItemAnimator mDefaultItemAnimator = new DefaultItemAnimator();
     private final ContentFillItemAnimator mLoadingContentItemAnimator =
             new ContentFillItemAnimator();
+    private final BaseActivity mActivity;
+    private final Drawable mStatusBarForegroundScrim;
 
     private RecentsToActivityHelper mActivityHelper;
     private RecyclerView mTaskRecyclerView;
@@ -117,6 +127,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++) {
@@ -136,8 +147,10 @@
 
     public IconRecentsView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        BaseActivity activity = BaseActivity.fromContext(context);
+        mActivity = BaseActivity.fromContext(context);
         mContext = context;
+        mStatusBarForegroundScrim  =
+                Themes.getAttrDrawable(mContext, R.attr.workspaceStatusBarScrim);
         mTaskLoader = new TaskListLoader(mContext);
         mTaskAdapter = new TaskAdapter(mTaskLoader);
         mTaskAdapter.setOnClearAllClickListener(view -> animateClearAllTasks());
@@ -155,7 +168,12 @@
             mTaskRecyclerView.setAdapter(mTaskAdapter);
             mTaskRecyclerView.setLayoutManager(mTaskLayoutManager);
             ItemTouchHelper helper = new ItemTouchHelper(
-                    new TaskSwipeCallback(mTaskActionController));
+                    new TaskSwipeCallback(holder -> {
+                        mTaskActionController.removeTask(holder);
+                        if (mTaskLoader.getCurrentTaskList().isEmpty()) {
+                            mActivityHelper.leaveRecents();
+                        }
+                    }));
             helper.attachToRecyclerView(mTaskRecyclerView);
             mTaskRecyclerView.addOnChildAttachStateChangeListener(
                     new OnChildAttachStateChangeListener() {
@@ -174,6 +192,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 +243,6 @@
                     updateContentViewVisibility();
                 }
             });
-            // TODO: Move layout param logic into onMeasure
         }
     }
 
@@ -296,6 +350,21 @@
     }
 
     /**
+     * Set whether or not to show the scrim in between the view and the top insets. This only works
+     * if the view is being insetted in the first place.
+     *
+     * The scrim is added to the activity's root view to prevent animations on this view
+     * affecting the scrim. As a result, it is the activity's responsibility to show/hide this
+     * scrim as appropriate.
+     *
+     * @param showStatusBarForegroundScrim true to show the scrim, false to hide
+     */
+    public void setShowStatusBarForegroundScrim(boolean showStatusBarForegroundScrim) {
+        boolean shouldShow = mInsets.top != 0 && showStatusBarForegroundScrim;
+        mActivity.getDragLayer().setForeground(shouldShow ? mStatusBarForegroundScrim : null);
+    }
+
+    /**
      * Get the bottom most thumbnail view to animate to.
      *
      * @return the thumbnail view if laid out
@@ -395,7 +464,6 @@
         if (mShowingContentView != mEmptyView && taskListSize == 0) {
             mShowingContentView = mEmptyView;
             crossfadeViews(mEmptyView, mContentView);
-            mActivityHelper.leaveRecents();
         }
         if (mShowingContentView != mContentView && taskListSize > 0) {
             mShowingContentView = mContentView;
@@ -482,4 +550,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..6db8013 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,10 +44,12 @@
     private final Drawable mDefaultThumbnail;
     private final TaskLayerDrawable mIconDrawable;
     private final TaskLayerDrawable mThumbnailDrawable;
+    private View mTaskIconThumbnailView;
     private TextView mLabelView;
     private ImageView mIconView;
     private ImageView mThumbnailView;
     private float mContentTransitionProgress;
+    private int mDisplayedOrientation;
 
     /**
      * Property representing the content transition progress of the view. 1.0f represents that the
@@ -81,6 +81,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 +92,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.
      */
@@ -186,14 +180,29 @@
     }
 
     @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        onOrientationChanged(getResources().getConfiguration().orientation);
+    }
+
+    @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
+        onOrientationChanged(newConfig.orientation);
+    }
+
+    private void onOrientationChanged(int newOrientation) {
+        if (mDisplayedOrientation == newOrientation) {
+            return;
+        }
+        mDisplayedOrientation = newOrientation;
         int layerCount = mThumbnailDrawable.getNumberOfLayers();
         for (int i = 0; i < layerCount; i++) {
             Drawable drawable = mThumbnailDrawable.getDrawable(i);
             if (drawable instanceof ThumbnailDrawable) {
-                ((ThumbnailDrawable) drawable).setRequestedOrientation(newConfig.orientation);
+                ((ThumbnailDrawable) drawable).setRequestedOrientation(newOrientation);
             }
         }
+        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/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
index bd78573..0b8c1c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.appprediction;
 
-import static com.android.launcher3.appprediction.PredictionUiStateManager.KEY_APP_SUGGESTION;
+import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
 
 import android.annotation.TargetApi;
 import android.app.prediction.AppPredictionContext;
@@ -26,8 +26,6 @@
 import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
@@ -35,12 +33,10 @@
 import android.util.Log;
 
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.util.UiThreadHelper;
 
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-
 import androidx.annotation.UiThread;
 import androidx.annotation.WorkerThread;
 
@@ -48,8 +44,7 @@
  * Subclass of app tracker which publishes the data to the prediction engine and gets back results.
  */
 @TargetApi(Build.VERSION_CODES.Q)
-public class PredictionAppTracker extends AppLaunchTracker
-        implements OnSharedPreferenceChangeListener {
+public class PredictionAppTracker extends AppLaunchTracker {
 
     private static final String TAG = "PredictionAppTracker";
     private static final boolean DBG = false;
@@ -62,8 +57,6 @@
     private final Context mContext;
     private final Handler mMessageHandler;
 
-    private boolean mEnabled;
-
     // Accessed only on worker thread
     private AppPredictor mHomeAppPredictor;
     private AppPredictor mRecentsOverviewPredictor;
@@ -71,24 +64,16 @@
     public PredictionAppTracker(Context context) {
         mContext = context;
         mMessageHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleMessage);
-
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        setEnabled(prefs.getBoolean(KEY_APP_SUGGESTION, true));
-        prefs.registerOnSharedPreferenceChangeListener(this);
         InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
+
+        mMessageHandler.sendEmptyMessage(MSG_INIT);
     }
 
     @UiThread
     private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        // Reinitialize everything
-        setEnabled(mEnabled);
-    }
-
-    @Override
-    @UiThread
-    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
-        if (KEY_APP_SUGGESTION.equals(key)) {
-            setEnabled(prefs.getBoolean(KEY_APP_SUGGESTION, true));
+        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
+            // Reinitialize everything
+            mMessageHandler.sendEmptyMessage(MSG_INIT);
         }
     }
 
@@ -137,13 +122,13 @@
                 return true;
             }
             case MSG_LAUNCH: {
-                if (mEnabled && mHomeAppPredictor != null) {
+                if (mHomeAppPredictor != null) {
                     mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj);
                 }
                 return true;
             }
             case MSG_PREDICT: {
-                if (mEnabled && mHomeAppPredictor != null) {
+                if (mHomeAppPredictor != null) {
                     String client = (String) msg.obj;
                     if (Client.HOME.id.equals(client)) {
                         mHomeAppPredictor.requestPredictionUpdate();
@@ -168,18 +153,6 @@
         }
     }
 
-    @UiThread
-    public void setEnabled(boolean isEnabled) {
-        mEnabled = isEnabled;
-        if (isEnabled) {
-            mMessageHandler.removeMessages(MSG_DESTROY);
-            mMessageHandler.sendEmptyMessage(MSG_INIT);
-        } else {
-            mMessageHandler.removeMessages(MSG_INIT);
-            mMessageHandler.sendEmptyMessage(MSG_DESTROY);
-        }
-    }
-
     @Override
     @UiThread
     public void onStartShortcut(String packageName, String shortcutId, UserHandle user,
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 54fd845..48a163d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -24,8 +24,7 @@
 import android.app.prediction.AppTarget;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Handler;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 
 import com.android.launcher3.AppInfo;
@@ -61,9 +60,10 @@
  * that client id.
  */
 public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInfoUpdateReceiver,
-        OnSharedPreferenceChangeListener, OnIDPChangeListener, OnUpdateListener {
+        OnIDPChangeListener, OnUpdateListener {
 
-    public static final String KEY_APP_SUGGESTION = "pref_show_predictions";
+    public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
+    private static final long INITIAL_CALLBACK_WAIT_TIMEOUT_MS = 5000;
 
     // TODO (b/129421797): Update the client constants
     public enum Client {
@@ -81,7 +81,6 @@
             new MainThreadInitializedObject<>(PredictionUiStateManager::new);
 
     private final Context mContext;
-    private final SharedPreferences mMainPrefs;
 
     private final DynamicItemCache mDynamicItemCache;
     private final List[] mPredictionServicePredictions;
@@ -94,9 +93,10 @@
     private PredictionState mPendingState;
     private PredictionState mCurrentState;
 
+    private boolean mGettingValidPredictionResults;
+
     private PredictionUiStateManager(Context context) {
         mContext = context;
-        mMainPrefs = Utilities.getPrefs(context);
 
         mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated);
 
@@ -110,8 +110,14 @@
         for (int i = 0; i < mPredictionServicePredictions.length; i++) {
             mPredictionServicePredictions[i] = Collections.emptyList();
         }
-        // Listens for enable/disable signal, and predictions if using AiAi is disabled.
-        mMainPrefs.registerOnSharedPreferenceChangeListener(this);
+
+        mGettingValidPredictionResults = Utilities.getDevicePrefs(context)
+                .getBoolean(LAST_PREDICTION_ENABLED_STATE, true);
+        if (mGettingValidPredictionResults) {
+            new Handler().postDelayed(
+                    this::updatePredictionStateAfterCallback, INITIAL_CALLBACK_WAIT_TIMEOUT_MS);
+        }
+
         // Call this last
         mCurrentState = parseLastState();
     }
@@ -177,13 +183,6 @@
         }
     }
 
-    @Override
-    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
-        if (KEY_APP_SUGGESTION.equals(key)) {
-            dispatchOnChange(true);
-        }
-    }
-
     private void applyState(PredictionState state) {
         boolean wasEnabled = mCurrentState.isEnabled;
         mCurrentState = state;
@@ -198,10 +197,24 @@
         }
     }
 
+    private void updatePredictionStateAfterCallback() {
+        boolean validResults = false;
+        for (List l : mPredictionServicePredictions) {
+            validResults |= l != null && !l.isEmpty();
+        }
+        if (validResults != mGettingValidPredictionResults) {
+            mGettingValidPredictionResults = validResults;
+            Utilities.getDevicePrefs(mContext).edit()
+                    .putBoolean(LAST_PREDICTION_ENABLED_STATE, true)
+                    .apply();
+        }
+        dispatchOnChange(true);
+    }
+
     public AppPredictor.Callback appPredictorCallback(Client client) {
         return targets -> {
             mPredictionServicePredictions[client.ordinal()] = targets;
-            dispatchOnChange(true);
+            updatePredictionStateAfterCallback();
         };
     }
 
@@ -217,7 +230,7 @@
 
     private PredictionState parseLastState() {
         PredictionState state = new PredictionState();
-        state.isEnabled = mMainPrefs.getBoolean(KEY_APP_SUGGESTION, true);
+        state.isEnabled = mGettingValidPredictionResults;
         if (!state.isEnabled) {
             state.apps = Collections.EMPTY_LIST;
             return state;
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/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
index a033402..50f25fb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -116,10 +116,12 @@
         } else {
             workspaceView = null;
         }
-        final Rect iconLocation = new Rect();
-        final FloatingIconView floatingView = workspaceView == null ? null
-                : FloatingIconView.getFloatingIconView(activity, workspaceView,
-                true /* hideOriginal */, iconLocation, false /* isOpening */, null /* recycle */);
+        final RectF iconLocation = new RectF();
+        boolean canUseWorkspaceView = workspaceView != null && workspaceView.isAttachedToWindow();
+        final FloatingIconView floatingView = canUseWorkspaceView
+                ? FloatingIconView.getFloatingIconView(activity, workspaceView,
+                true /* hideOriginal */, iconLocation, false /* isOpening */, null /* recycle */)
+                : null;
 
         return new HomeAnimationFactory() {
             @Nullable
@@ -135,8 +137,8 @@
                 final float targetCenterX = dp.availableWidthPx / 2f;
                 final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
 
-                if (workspaceView != null) {
-                    return new RectF(iconLocation);
+                if (canUseWorkspaceView) {
+                    return iconLocation;
                 } else {
                     // Fallback to animate to center of screen.
                     return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
@@ -308,7 +310,10 @@
         SCALE_PROPERTY.set(recentsView, targetRvScale);
         recentsView.setTranslationY(0);
         ClipAnimationHelper clipHelper = new ClipAnimationHelper(launcher);
+        float tmpCurveScale = v.getCurveScale();
+        v.setCurveScale(1f);
         clipHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(), null);
+        v.setCurveScale(tmpCurveScale);
         SCALE_PROPERTY.set(recentsView, prevRvScale);
         recentsView.setTranslationY(prevRvTransY);
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
index 5dc641f..7fc5d50 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;
@@ -34,20 +35,17 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.graphics.PointF;
-import android.graphics.Rect;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.view.Display;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 
-import androidx.annotation.UiThread;
-
 import com.android.launcher3.R;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RaceConditionTracker;
 import com.android.launcher3.util.TraceHelper;
@@ -61,10 +59,11 @@
 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.WindowManagerWrapper;
 
 import java.util.function.Consumer;
 
+import androidx.annotation.UiThread;
+
 /**
  * Input consumer for handling events originating from an activity other than Launcher
  */
@@ -80,14 +79,12 @@
     private final Intent mHomeIntent;
     private final ActivityControlHelper mActivityControlHelper;
     private final OverviewCallbacks mOverviewCallbacks;
-    private final TaskOverlayFactory mTaskOverlayFactory;
     private final InputConsumerController mInputConsumer;
     private final SwipeSharedState mSwipeSharedState;
     private final InputMonitorCompat mInputMonitorCompat;
     private final SysUINavigationMode.Mode mMode;
 
     private final int mDisplayRotation;
-    private final Rect mStableInsets = new Rect();
 
     private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
     private final MotionPauseDetector mMotionPauseDetector;
@@ -122,7 +119,7 @@
     public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo,
             RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
             boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks,
-            TaskOverlayFactory taskOverlayFactory, InputConsumerController inputConsumer,
+            InputConsumerController inputConsumer,
             Consumer<OtherActivityInputConsumer> onCompleteCallback,
             SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat) {
         super(base);
@@ -144,14 +141,10 @@
         boolean continuingPreviousGesture = swipeSharedState.getActiveListener() != null;
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
         mOverviewCallbacks = overviewCallbacks;
-        mTaskOverlayFactory = taskOverlayFactory;
         mInputConsumer = inputConsumer;
         mSwipeSharedState = swipeSharedState;
 
-        Display display = getSystemService(WindowManager.class).getDefaultDisplay();
-        mDisplayRotation = display.getRotation();
-        WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
-
+        mDisplayRotation = getSystemService(WindowManager.class).getDefaultDisplay().getRotation();
         mDragSlop = QuickStepContract.getQuickStepDragSlopPx();
         mTouchSlop = QuickStepContract.getQuickStepTouchSlopPx();
 
@@ -170,16 +163,17 @@
         }
 
         // Proxy events to recents view
-        if (!isNavBarOnLeft() && !isNavBarOnRight()) {
-            if (mPassedDragSlop && mInteractionHandler != null
-                    && !mRecentsViewDispatcher.hasConsumer()) {
-                mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher());
-            }
-            int edgeFlags = ev.getEdgeFlags();
-            ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
-            mRecentsViewDispatcher.dispatchEvent(ev);
-            ev.setEdgeFlags(edgeFlags);
+        if (mPassedDragSlop && mInteractionHandler != null
+                && !mRecentsViewDispatcher.hasConsumer()) {
+            mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
+                    isNavBarOnLeft()
+                            ? RotationMode.SEASCAPE
+                            : (isNavBarOnRight() ? RotationMode.LANDSCAPE : RotationMode.NORMAL)));
         }
+        int edgeFlags = ev.getEdgeFlags();
+        ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
+        mRecentsViewDispatcher.dispatchEvent(ev);
+        ev.setEdgeFlags(edgeFlags);
 
         mVelocityTracker.addMovement(ev);
         if (ev.getActionMasked() == ACTION_POINTER_UP) {
@@ -301,13 +295,11 @@
     }
 
     private boolean isNavBarOnRight() {
-        return SysUINavigationMode.INSTANCE.get(getBaseContext()).getMode() != NO_BUTTON
-                && mDisplayRotation == Surface.ROTATION_90 && mStableInsets.right > 0;
+        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_90;
     }
 
     private boolean isNavBarOnLeft() {
-        return SysUINavigationMode.INSTANCE.get(getBaseContext()).getMode() != NO_BUTTON
-                && mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0;
+        return mMode != NO_BUTTON && mDisplayRotation == Surface.ROTATION_270;
     }
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
@@ -345,18 +337,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 +382,7 @@
             mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
             mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
             if (mSwipeSharedState.canGestureBeContinued) {
-                mInteractionHandler.cancel();
+                mInteractionHandler.cancelCurrentAnimation();
             } else {
                 mInteractionHandler.reset();
             }
@@ -410,19 +405,22 @@
     }
 
     private float getDisplacement(MotionEvent ev) {
-        float eventX = ev.getX();
-        float eventY = ev.getY();
-        float displacement = eventY - mDownPos.y;
         if (isNavBarOnRight()) {
-            displacement = eventX - mDownPos.x;
+            return ev.getX() - mDownPos.x;
         } else if (isNavBarOnLeft()) {
-            displacement = mDownPos.x - eventX;
+            return mDownPos.x - ev.getX();
+        } else {
+            return ev.getY() - mDownPos.y;
         }
-        return displacement;
     }
 
     @Override
     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 8bda3f6..2bb82e1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
@@ -15,24 +15,22 @@
  */
 package com.android.quickstep;
 
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_UP;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
-import android.graphics.PointF;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.ViewConfiguration;
+
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+import java.util.function.Predicate;
 
 /**
  * Input consumer for handling touch on the recents/Launcher activity.
@@ -40,24 +38,33 @@
 public class OverviewInputConsumer<T extends BaseDraggingActivity>
         implements InputConsumer {
 
-    private final CachedEventDispatcher mCachedEventDispatcher = new CachedEventDispatcher();
     private final T mActivity;
     private final BaseDragLayer mTarget;
+    private final InputMonitorCompat mInputMonitor;
+
     private final int[] mLocationOnScreen = new int[2];
-    private final PointF mDownPos = new PointF();
-    private final int mTouchSlopSquared;
+    private final boolean mProxyTouch;
+    private final Predicate<MotionEvent> mEventReceiver;
 
     private final boolean mStartingInActivityBounds;
+    private boolean mTargetHandledTouch;
 
-    private boolean mTrackingStarted = false;
-    private boolean mInvalidated = false;
-
-    OverviewInputConsumer(T activity, boolean startingInActivityBounds) {
+    OverviewInputConsumer(T activity, @Nullable InputMonitorCompat inputMonitor,
+            boolean startingInActivityBounds) {
         mActivity = activity;
-        mTarget = activity.getDragLayer();
-        int touchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop();
-        mTouchSlopSquared = touchSlop * touchSlop;
+        mInputMonitor = inputMonitor;
         mStartingInActivityBounds = startingInActivityBounds;
+
+        mTarget = activity.getDragLayer();
+        if (startingInActivityBounds) {
+            mEventReceiver = mTarget::dispatchTouchEvent;
+            mProxyTouch = true;
+        } else {
+            // Only proxy touches to controllers if we are starting touch from nav bar.
+            mEventReceiver = mTarget::proxyTouchEvent;
+            mTarget.getLocationOnScreen(mLocationOnScreen);
+            mProxyTouch = mTarget.prepareProxyEventStarting();
+        }
     }
 
     @Override
@@ -66,46 +73,35 @@
     }
 
     @Override
+    public boolean allowInterceptByParent() {
+        return !mTargetHandledTouch;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
-        if (mInvalidated) {
+        if (!mProxyTouch) {
             return;
         }
-        mCachedEventDispatcher.dispatchEvent(ev);
-        int action = ev.getActionMasked();
-        if (action == ACTION_DOWN) {
-            if (mStartingInActivityBounds) {
-                startTouchTracking(ev, false /* updateLocationOffset */,
-                        false /* closeActiveWindows */);
-                return;
-            }
-            mTrackingStarted = false;
-            mDownPos.set(ev.getX(), ev.getY());
-        } else if (!mTrackingStarted) {
-            switch (action) {
-                case ACTION_CANCEL:
-                case ACTION_UP:
-                    startTouchTracking(ev, true /* updateLocationOffset */,
-                            false /* closeActiveWindows */);
-                    break;
-                case ACTION_MOVE: {
-                    float x = ev.getX() - mDownPos.x;
-                    float y = ev.getY() - mDownPos.y;
-                    double hypotSquared = x * x + y * y;
-                    if (hypotSquared >= mTouchSlopSquared) {
-                        // Start tracking only when touch slop is crossed.
-                        startTouchTracking(ev, true /* updateLocationOffset */,
-                                true /* closeActiveWindows */);
-                    }
-                }
-            }
+
+        int flags = ev.getEdgeFlags();
+        if (!mStartingInActivityBounds) {
+            ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
         }
+        ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
+        boolean handled = mEventReceiver.test(ev);
+        ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
+        ev.setEdgeFlags(flags);
 
-        if (action == ACTION_UP || action == ACTION_CANCEL) {
-            mInvalidated = true;
-
-            // Set an empty consumer to that all the cached events are cleared
-            if (!mCachedEventDispatcher.hasConsumer()) {
-                mCachedEventDispatcher.setConsumer(motionEvent -> { });
+        if (!mTargetHandledTouch && handled) {
+            mTargetHandledTouch = true;
+            if (!mStartingInActivityBounds) {
+                OverviewCallbacks.get(mActivity).closeAllWindows();
+                ActivityManagerWrapper.getInstance()
+                        .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+                TOUCH_INTERACTION_LOG.addLog("startQuickstep");
+            }
+            if (mInputMonitor != null) {
+                mInputMonitor.pilferPointers();
             }
         }
     }
@@ -117,42 +113,12 @@
         }
     }
 
-    private void startTouchTracking(MotionEvent ev, boolean updateLocationOffset,
-            boolean closeActiveWindows) {
-        if (updateLocationOffset) {
-            mTarget.getLocationOnScreen(mLocationOnScreen);
-        }
-
-        if (closeActiveWindows) {
-            OverviewCallbacks.get(mActivity).closeAllWindows();
-            ActivityManagerWrapper.getInstance()
-                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-            TOUCH_INTERACTION_LOG.addLog("startQuickstep");
-        }
-
-        mTrackingStarted = true;
-        mCachedEventDispatcher.setConsumer(this::sendEvent);
-
-    }
-
-    private void sendEvent(MotionEvent ev) {
-        if (mInvalidated) {
-            return;
-        }
-        int flags = ev.getEdgeFlags();
-        ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
-        ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
-        mInvalidated = !mTarget.dispatchTouchEvent(this, ev);
-        ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
-        ev.setEdgeFlags(flags);
-    }
-
     public static InputConsumer newInstance(ActivityControlHelper activityHelper,
-            boolean startingInActivityBounds) {
+            @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
         BaseDraggingActivity activity = activityHelper.getCreatedActivity();
         if (activity == null) {
             return InputConsumer.NO_OP;
         }
-        return new OverviewInputConsumer(activity, startingInActivityBounds);
+        return new OverviewInputConsumer(activity, inputMonitor, startingInActivityBounds);
     }
 }
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 fc3f332..b25865e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -20,15 +20,17 @@
 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;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.KeyguardManager;
 import android.app.Service;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -44,6 +46,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.Display;
@@ -53,6 +56,7 @@
 import android.view.WindowManager;
 
 import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.UserManagerCompat;
 import com.android.launcher3.logging.EventLogArray;
@@ -67,9 +71,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;
@@ -78,7 +81,7 @@
 /**
  * Service connected by system-UI for handling touch interaction.
  */
-@TargetApi(Build.VERSION_CODES.O)
+@TargetApi(Build.VERSION_CODES.Q)
 public class TouchInteractionService extends Service implements
         NavigationModeChangeListener, DisplayListener {
 
@@ -229,6 +232,7 @@
     private Mode mMode = Mode.THREE_BUTTONS;
     private int mDefaultDisplayId;
     private final RectF mSwipeTouchRegion = new RectF();
+    private ComponentName mGestureBlockingActivity;
 
     @Override
     public void onCreate() {
@@ -250,6 +254,10 @@
 
         mDefaultDisplayId = getSystemService(WindowManager.class).getDefaultDisplay()
                 .getDisplayId();
+
+        String blockingActivity = getString(R.string.gesture_blocking_activity);
+        mGestureBlockingActivity = TextUtils.isEmpty(blockingActivity) ? null :
+                ComponentName.unflattenFromString(blockingActivity);
         sConnected = true;
     }
 
@@ -435,9 +443,9 @@
     }
 
     private boolean isInValidSystemUiState() {
-        return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0;
+        return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+                && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
+                && !ActivityManagerWrapper.getInstance().isLockToAppActive();
     }
 
     private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) {
@@ -456,25 +464,35 @@
         final ActivityControlHelper activityControl =
                 mOverviewComponentObserver.getActivityControlHelper();
 
+        InputConsumer base;
         if (runningTaskInfo == null && !mSwipeSharedState.goingToLauncher) {
-            return InputConsumer.NO_OP;
-        } else if (mAssistantAvailable
-                && SysUINavigationMode.INSTANCE.get(this).getMode() == Mode.NO_BUTTON
-                && AssistantTouchConsumer.withinTouchRegion(this, event)) {
-
-            boolean addDelegate = !activityControl.isResumed();
-            return new AssistantTouchConsumer(this, mISystemUiProxy, addDelegate ?
-                    createOtherActivityInputConsumer(event, runningTaskInfo) : null,
-                    mInputMonitorCompat, activityControl);
-
+            base = InputConsumer.NO_OP;
         } else if (mSwipeSharedState.goingToLauncher || activityControl.isResumed()) {
-            return OverviewInputConsumer.newInstance(activityControl, false);
+            base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
         } else if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
                 activityControl.isInLiveTileMode()) {
-            return OverviewInputConsumer.newInstance(activityControl, false);
+            base = OverviewInputConsumer.newInstance(activityControl, mInputMonitorCompat, false);
+        } else if (mGestureBlockingActivity != null && runningTaskInfo != null
+                && mGestureBlockingActivity.equals(runningTaskInfo.topActivity)) {
+            base = InputConsumer.NO_OP;
         } 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,
@@ -484,8 +502,8 @@
         boolean shouldDefer = activityControl.deferStartingActivity(mActiveNavBarRegion, event);
         return new OtherActivityInputConsumer(this, runningTaskInfo, mRecentsModel,
                 mOverviewComponentObserver.getOverviewIntent(), activityControl,
-                shouldDefer, mOverviewCallbacks, mTaskOverlayFactory, mInputConsumer,
-                this::onConsumerInactive, mSwipeSharedState, mInputMonitorCompat);
+                shouldDefer, mOverviewCallbacks, mInputConsumer, this::onConsumerInactive,
+                mSwipeSharedState, mInputMonitorCompat);
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index afc4fcb..9c6102e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -77,6 +77,7 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -220,6 +221,8 @@
 
     protected Runnable mGestureEndCallback;
     protected GestureEndTarget mGestureEndTarget;
+    // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
+    private RunningWindowAnim mRunningWindowAnim;
     private boolean mIsShelfPeeking;
     private DeviceProfile mDp;
     private int mTransitionDragLength;
@@ -250,7 +253,6 @@
 
     private T mActivity;
     private RecentsView mRecentsView;
-    private SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier;
     private AnimationFactory mAnimationFactory = (t) -> { };
     private LiveTileOverlay mLiveTileOverlay = new LiveTileOverlay();
 
@@ -405,8 +407,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) -> {
@@ -529,8 +534,8 @@
         return TaskView.getCurveScaleForInterpolation(interpolation);
     }
 
-    public Consumer<MotionEvent> getRecentsViewDispatcher() {
-        return mRecentsView != null ? mRecentsView::dispatchTouchEvent : null;
+    public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
+        return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
     }
 
     @UiThread
@@ -648,8 +653,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 +733,7 @@
         if (runningTaskTarget != null) {
             mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
         }
-        mClipAnimationHelper.prepareAnimation(false /* isOpening */);
+        mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */);
         initTransitionEndpoints(dp);
 
         mRecentsAnimationWrapper.setController(targetSet);
@@ -769,6 +773,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,12 +803,12 @@
             mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
         }
         mDownPos = downPos;
-        handleNormalGestureEnd(endVelocity, isFling, velocity);
+        handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
     @UiThread
     private InputConsumer createNewInputProxyHandler() {
-        mCurrentShift.finishAnimation();
+        endRunningWindowAnim();
         if (mLauncherTransitionController != null) {
             mLauncherTransitionController.getAnimationPlayer().end();
         }
@@ -802,11 +817,18 @@
             setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
         }
 
-        return OverviewInputConsumer.newInstance(mActivityControlHelper, true);
+        return OverviewInputConsumer.newInstance(mActivityControlHelper, null, true);
+    }
+
+    private void endRunningWindowAnim() {
+        if (mRunningWindowAnim != null) {
+            mRunningWindowAnim.end();
+        }
     }
 
     @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 +846,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) {
@@ -883,12 +907,15 @@
             }
         }
 
+        if (endTarget.isLauncher) {
+            mRecentsAnimationWrapper.enableInputProxy();
+        }
+
         if (endTarget == HOME) {
             setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
         } else if (endTarget == RECENTS) {
             mLiveTileOverlay.startIconAnimation();
-            mRecentsAnimationWrapper.enableInputProxy();
             if (mRecentsView != null) {
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
@@ -907,7 +934,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;
         }
@@ -969,6 +996,7 @@
                 }
             });
             windowAnim.start(velocityPxPerMs);
+            mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
             mLauncherTransitionController = null;
         } else {
             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
@@ -987,6 +1015,7 @@
                 }
             });
             windowAnim.start();
+            mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
         }
         // Always play the entire launcher animation when going home, since it is separate from
         // the animation that has been controlled thus far.
@@ -1047,8 +1076,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 +1090,9 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 homeAnim.dispatchOnStart();
-                mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+                if (mActivity != null) {
+                    mActivity.getRootView().getOverlay().remove(mLiveTileOverlay);
+                }
             }
 
             @Override
@@ -1097,7 +1127,7 @@
                         false /* animate */, true /* freezeTaskList */);
             });
         }
-        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", false);
+        TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true);
         doLogGesture(NEW_TASK);
         reset();
     }
@@ -1106,7 +1136,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()) {
@@ -1115,7 +1149,7 @@
     }
 
     private void invalidateHandler() {
-        mCurrentShift.finishAnimation();
+        endRunningWindowAnim();
 
         if (mGestureEndCallback != null) {
             mGestureEndCallback.run();
@@ -1263,4 +1297,16 @@
         return app.isNotInRecents
                 || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
     }
+
+    private interface RunningWindowAnim {
+        void end();
+
+        static RunningWindowAnim wrap(Animator animator) {
+            return animator::end;
+        }
+
+        static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) {
+            return rectFSpringAnim::end;
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 777e592..09d323e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -17,18 +17,13 @@
 
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.ViewDebug;
 import android.view.WindowInsets;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
@@ -39,9 +34,6 @@
     private static final int MIN_SIZE = 10;
     private final RecentsActivity mActivity;
 
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final RectF mTouchExcludeRegion = new RectF();
-
     private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
 
     public RecentsRootView(Context context, AttributeSet attrs) {
@@ -100,26 +92,7 @@
 
     @Override
     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
-        if (Utilities.ATLEAST_Q) {
-            Insets gestureInsets = insets.getMandatorySystemGestureInsets();
-            mTouchExcludeRegion.set(gestureInsets.left, gestureInsets.top,
-                    gestureInsets.right, gestureInsets.bottom);
-        }
+        updateTouchExcludeRegion(insets);
         return super.dispatchApplyWindowInsets(insets);
     }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            float x = ev.getX();
-            float y = ev.getY();
-            if (y < mTouchExcludeRegion.top
-                    || x < mTouchExcludeRegion.left
-                    || x > (getWidth() - mTouchExcludeRegion.right)
-                    || y > (getHeight() - mTouchExcludeRegion.bottom)) {
-                return false;
-            }
-        }
-        return super.dispatchTouchEvent(ev);
-    }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java
index 71f33c3..c9f4ab4 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
@@ -31,11 +31,12 @@
 import android.os.Build;
 import android.os.RemoteException;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.views.RecentsView;
@@ -50,8 +51,6 @@
 
 import java.util.function.BiFunction;
 
-import androidx.annotation.Nullable;
-
 /**
  * Utility class to handle window clip animation
  */
@@ -81,6 +80,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 +89,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 +105,7 @@
         mWindowCornerRadius = getWindowCornerRadius(context.getResources());
         mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
         mTaskCornerRadius = TaskCornerRadius.get(context);
+        mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows;
     }
 
     private void updateSourceStack(RemoteAnimationTargetCompat target) {
@@ -144,8 +147,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 +181,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 +194,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;
                     }
@@ -318,12 +326,14 @@
 
         float scale = mTargetRect.width() / mSourceRect.width();
         float insetProgress = (1 - progress);
+        float windowCornerRadius = mUseRoundedCornersOnWindows
+                ? mWindowCornerRadius : 0;
         ttv.drawOnCanvas(canvas,
                 -mSourceWindowClipInsets.left * insetProgress,
                 -mSourceWindowClipInsets.top * insetProgress,
                 ttv.getMeasuredWidth() + mSourceWindowClipInsets.right * insetProgress,
                 ttv.getMeasuredHeight() + mSourceWindowClipInsets.bottom * insetProgress,
-                Utilities.mapRange(progress, mWindowCornerRadius * scale, ttv.getCornerRadius()));
+                Utilities.mapRange(progress, windowCornerRadius * scale, ttv.getCornerRadius()));
     }
 
     public RectF getTargetRect() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index 2edeb3a..7159e7c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -23,6 +23,9 @@
 import android.graphics.RectF;
 import android.util.FloatProperty;
 
+import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.FlingSpringAnim;
@@ -30,9 +33,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-
 /**
  * Applies spring forces to animate from a starting rect to a target rect,
  * while providing update callbacks to the caller.
@@ -98,6 +98,10 @@
     private float mCurrentCenterX;
     private float mCurrentCenterY;
     private float mCurrentScaleProgress;
+    private FlingSpringAnim mRectXAnim;
+    private FlingSpringAnim mRectYAnim;
+    private ValueAnimator mRectScaleAnim;
+    private boolean mAnimsStarted;
     private boolean mRectXAnimEnded;
     private boolean mRectYAnimEnded;
     private boolean mRectScaleAnimEnded;
@@ -127,15 +131,15 @@
             mRectYAnimEnded = true;
             maybeOnEnd();
         });
-        FlingSpringAnim rectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX,
+        mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, mCurrentCenterX,
                 mTargetRect.centerX(), velocityPxPerMs.x * 1000, onXEndListener);
-        FlingSpringAnim rectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY,
+        mRectYAnim = new FlingSpringAnim(this, RECT_CENTER_Y, mCurrentCenterY,
                 mTargetRect.centerY(), velocityPxPerMs.y * 1000, onYEndListener);
 
-        ValueAnimator rectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+        mRectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this,
                 PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1))
                 .setDuration(RECT_SCALE_DURATION);
-        rectScaleAnim.addListener(new AnimationSuccessListener() {
+        mRectScaleAnim.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationSuccess(Animator animator) {
                 mRectScaleAnimEnded = true;
@@ -143,14 +147,23 @@
             }
         });
 
-        rectXAnim.start();
-        rectYAnim.start();
-        rectScaleAnim.start();
+        mRectXAnim.start();
+        mRectYAnim.start();
+        mRectScaleAnim.start();
+        mAnimsStarted = true;
         for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
             animatorListener.onAnimationStart(null);
         }
     }
 
+    public void end() {
+        if (mAnimsStarted) {
+            mRectXAnim.end();
+            mRectYAnim.end();
+            mRectScaleAnim.end();
+        }
+    }
+
     private void onUpdate() {
         if (!mOnUpdateListeners.isEmpty()) {
             float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(),
@@ -166,7 +179,8 @@
     }
 
     private void maybeOnEnd() {
-        if (mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
+        if (mAnimsStarted && mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) {
+            mAnimsStarted = false;
             for (Animator.AnimatorListener animatorListener : mAnimatorListeners) {
                 animatorListener.onAnimationEnd(null);
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
index 5a1a103..83973fa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java
@@ -95,9 +95,4 @@
 
         void onRecentsAnimationCanceled();
     }
-
-    public interface SwipeAnimationFinishListener {
-
-        void onSwipeAnimationFinished(SwipeAnimationTargetSet targetSet);
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java
index 10283bf..bb41e5d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -25,6 +25,7 @@
 import android.util.FloatProperty;
 import android.view.View;
 
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.Utilities;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
@@ -52,7 +53,7 @@
 
     private final RecentsView mParent;
     private final View mIconView;
-    private final int[] mIconPos;
+    private final float[] mIconPos;
     private final TaskView mTaskView;
 
     private final TaskThumbnailView mThumbnailView;
@@ -68,13 +69,15 @@
         mParent = parent;
         mTaskView = tv;
         mIconView = tv.getIconView();
-        mIconPos = new int[2];
+        mIconPos = new float[2];
         mIconScale = mIconView.getScaleX();
         Utilities.getDescendantCoordRelativeToAncestor(mIconView, parent, mIconPos, true);
 
         mThumbnailView = tv.getThumbnail();
         mClipAnimationHelper = new ClipAnimationHelper(parent.getContext());
         mClipAnimationHelper.fromTaskThumbnailView(mThumbnailView, parent);
+        mClipAnimationHelper.prepareAnimation(
+                BaseActivity.fromContext(tv.getContext()).getDeviceProfile(), true /* isOpening */);
     }
 
     public void setProgress(float progress) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 08a7616..b5f90a5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -45,6 +45,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -70,9 +71,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ListView;
 
-import androidx.annotation.Nullable;
-import androidx.dynamicanimation.animation.SpringForce;
-
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
@@ -85,6 +83,7 @@
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -111,6 +110,9 @@
 import java.util.ArrayList;
 import java.util.function.Consumer;
 
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.SpringForce;
+
 /**
  * A list of recent tasks.
  */
@@ -1639,4 +1641,26 @@
     public ClearAllButton getClearAllButton() {
         return mClearAllButton;
     }
+
+    public Consumer<MotionEvent> getEventDispatcher(RotationMode rotationMode) {
+        if (rotationMode.isTransposed) {
+            Matrix transform = new Matrix();
+            transform.setRotate(-rotationMode.surfaceRotation);
+
+            if (getWidth() > 0 && getHeight() > 0) {
+                float scale = ((float) getWidth()) / getHeight();
+                transform.postScale(scale, 1 / scale);
+            }
+
+            Matrix inverse = new Matrix();
+            transform.invert(inverse);
+            return e -> {
+                e.transform(transform);
+                super.onTouchEvent(e);
+                e.transform(inverse);
+            };
+        } else {
+            return super::onTouchEvent;
+        }
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index ed68d87..7e15d52 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -51,7 +51,6 @@
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.QuickStepContract;
 
 /**
  * A task in the Recents view.
@@ -60,6 +59,7 @@
 
     private final static ColorMatrix COLOR_MATRIX = new ColorMatrix();
     private final static ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
+    private final static Rect EMPTY_RECT = new Rect();
 
     public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
             new FloatProperty<TaskThumbnailView>("dimAlpha") {
@@ -79,16 +79,17 @@
     private final BaseActivity mActivity;
     private final TaskOverlay mOverlay;
     private final boolean mIsDarkTextTheme;
-    private final Paint mPaint = new Paint();
-    private final Paint mBackgroundPaint = new Paint();
+    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mClearPaint = new Paint();
     private final Paint mDimmingPaintAfterClearing = new Paint();
-    private final float mWindowCornerRadius;
 
     private final Matrix mMatrix = new Matrix();
 
     private float mClipBottom = -1;
     private Rect mScaledInsets = new Rect();
+    private Rect mCurrentDrawnInsets = new Rect();
+    private float mCurrentDrawnCornerRadius;
     private boolean mIsRotated;
 
     private Task mTask;
@@ -117,7 +118,7 @@
         mDimmingPaintAfterClearing.setColor(Color.BLACK);
         mActivity = BaseActivity.fromContext(context);
         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
-        mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
+        setCurrentDrawnInsetsAndRadius(EMPTY_RECT, mCornerRadius);
     }
 
     public void bind(Task task) {
@@ -200,25 +201,24 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        TaskView taskView = (TaskView) getParent();
-        float fullscreenProgress = taskView.getFullscreenProgress();
-        if (mIsRotated) {
-            // Don't show insets in the wrong orientation.
-            fullscreenProgress = 0;
-        }
-        if (fullscreenProgress > 0) {
-            // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
-            float cornerRadius = Utilities.mapRange(fullscreenProgress, mCornerRadius,
-                    mWindowCornerRadius);
-            drawOnCanvas(canvas,
-                    -mScaledInsets.left * fullscreenProgress,
-                    -mScaledInsets.top * fullscreenProgress,
-                    getMeasuredWidth() + mScaledInsets.right * fullscreenProgress,
-                    getMeasuredHeight() + mScaledInsets.bottom * fullscreenProgress,
-                    cornerRadius / taskView.getRecentsView().getScaleX());
-        } else {
-            drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
-        }
+        // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
+        drawOnCanvas(canvas,
+                -mCurrentDrawnInsets.left,
+                -mCurrentDrawnInsets.top,
+                getMeasuredWidth() + mCurrentDrawnInsets.right,
+                getMeasuredHeight() + mCurrentDrawnInsets.bottom,
+                mCurrentDrawnCornerRadius);
+    }
+
+    public Rect getInsetsToDrawInFullscreen(boolean isMultiWindowMode) {
+        // Don't show insets in the wrong orientation or in multi window mode.
+        return mIsRotated || isMultiWindowMode ? EMPTY_RECT : mScaledInsets;
+    }
+
+    public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) {
+        mCurrentDrawnInsets.set(insets);
+        mCurrentDrawnCornerRadius = radius;
+        invalidate();
     }
 
     public float getCornerRadius() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 2f739c8..6cd46d9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -17,7 +17,7 @@
 package com.android.quickstep.views;
 
 import static android.widget.Toast.LENGTH_SHORT;
-import static com.android.launcher3.BaseActivity.fromContext;
+
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -31,6 +31,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Outline;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
@@ -46,6 +47,7 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
@@ -53,7 +55,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.PendingAnimation;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
@@ -61,12 +62,15 @@
 import com.android.quickstep.TaskSystemShortcut;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.views.RecentsView.PageCallbacks;
 import com.android.quickstep.views.RecentsView.ScrollState;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.QuickStepContract;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -95,6 +99,9 @@
     public static final long SCALE_ICON_DURATION = 120;
     private static final long DIM_ANIM_DURATION = 700;
 
+    private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
+            Collections.singletonList(new Rect());
+
     public static final Property<TaskView, Float> ZOOM_SCALE =
             new FloatProperty<TaskView>("zoomScale") {
                 @Override
@@ -149,6 +156,8 @@
                 }
             };
 
+    private final TaskOutlineProvider mOutlineProvider;
+
     private Task mTask;
     private TaskThumbnailView mSnapshotView;
     private TaskMenuView mMenuView;
@@ -157,6 +166,10 @@
     private float mCurveScale;
     private float mZoomScale;
     private float mFullscreenProgress;
+    private final Rect mCurrentDrawnInsets = new Rect();
+    private final float mCornerRadius;
+    private final float mWindowCornerRadius;
+    private final BaseDraggingActivity mActivity;
 
     private ObjectAnimator mIconAndDimAnimator;
     private float mIconScaleAnimStartProgress = 0;
@@ -178,6 +191,7 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        mActivity = BaseDraggingActivity.fromContext(context);
         setOnClickListener((view) -> {
             if (getTask() == null) {
                 return;
@@ -192,13 +206,16 @@
                 launchTask(true /* animate */);
             }
 
-            fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss(
+            mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
                     Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
                     TaskUtils.getLaunchComponentKeyForTask(getTask().key));
-            fromContext(context).getStatsLogManager().logTaskLaunch(getRecentsView(),
+            mActivity.getStatsLogManager().logTaskLaunch(getRecentsView(),
                     TaskUtils.getLaunchComponentKeyForTask(getTask().key));
         });
-        setOutlineProvider(new TaskOutlineProvider(context, getResources()));
+        mCornerRadius = TaskCornerRadius.get(context);
+        mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
+        mOutlineProvider = new TaskOutlineProvider(getResources(), mCornerRadius);
+        setOutlineProvider(mOutlineProvider);
     }
 
     @Override
@@ -291,8 +308,7 @@
         if (mTask != null) {
             final ActivityOptions opts;
             if (animate) {
-                opts = ((BaseDraggingActivity) fromContext(getContext()))
-                        .getActivityLaunchOptions(this);
+                opts = mActivity.getActivityLaunchOptions(this);
                 if (freezeTaskList) {
                     ActivityOptionsCompat.setFreezeRecentTasksList(opts);
                 }
@@ -480,6 +496,10 @@
         super.onLayout(changed, left, top, right, bottom);
         setPivotX((right - left) * 0.5f);
         setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
+        if (Utilities.ATLEAST_Q) {
+            SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight());
+            setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
+        }
     }
 
     public static float getCurveScaleForInterpolation(float linearInterpolation) {
@@ -491,7 +511,7 @@
         return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
     }
 
-    private void setCurveScale(float curveScale) {
+    public void setCurveScale(float curveScale) {
         mCurveScale = curveScale;
         onScaleChanged();
     }
@@ -520,17 +540,26 @@
     private static final class TaskOutlineProvider extends ViewOutlineProvider {
 
         private final int mMarginTop;
-        private final float mRadius;
+        private final Rect mInsets = new Rect();
+        private float mRadius;
 
-        TaskOutlineProvider(Context context, Resources res) {
+        TaskOutlineProvider(Resources res, float radius) {
             mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-            mRadius = Themes.getDialogCornerRadius(context);
+            mRadius = radius;
+        }
+
+        public void setCurrentDrawnInsetsAndRadius(Rect insets, float radius) {
+            mInsets.set(insets);
+            mRadius = radius;
         }
 
         @Override
         public void getOutline(View view, Outline outline) {
-            outline.setRoundRect(0, mMarginTop, view.getWidth(),
-                    view.getHeight(), mRadius);
+            outline.setRoundRect(-mInsets.left,
+                    mMarginTop - mInsets.top,
+                    view.getWidth() + mInsets.right,
+                    view.getHeight() + mInsets.bottom,
+                    mRadius);
         }
     }
 
@@ -543,13 +572,12 @@
                         getContext().getText(R.string.accessibility_close_task)));
 
         final Context context = getContext();
-        final BaseDraggingActivity activity = fromContext(context);
         final List<TaskSystemShortcut> shortcuts =
                 mSnapshotView.getTaskOverlay().getEnabledShortcuts(this);
         final int count = shortcuts.size();
         for (int i = 0; i < count; ++i) {
             final TaskSystemShortcut menuOption = shortcuts.get(i);
-            OnClickListener onClickListener = menuOption.getOnClickListener(activity, this);
+            OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this);
             if (onClickListener != null) {
                 info.addAction(menuOption.createAccessibilityAction(context));
             }
@@ -589,8 +617,7 @@
         for (int i = 0; i < count; ++i) {
             final TaskSystemShortcut menuOption = shortcuts.get(i);
             if (menuOption.hasHandlerForAction(action)) {
-                OnClickListener onClickListener = menuOption.getOnClickListener(
-                        fromContext(getContext()), this);
+                OnClickListener onClickListener = menuOption.getOnClickListener(mActivity, this);
                 if (onClickListener != null) {
                     onClickListener.onClick(this);
                 }
@@ -628,11 +655,21 @@
         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
         setClipChildren(!isFullscreen);
         setClipToPadding(!isFullscreen);
-        getThumbnail().invalidate();
-    }
 
-    public float getFullscreenProgress() {
-        return mFullscreenProgress;
+        TaskThumbnailView thumbnail = getThumbnail();
+        boolean isMultiWindowMode = mActivity.getDeviceProfile().isMultiWindowMode;
+        Rect insets = thumbnail.getInsetsToDrawInFullscreen(isMultiWindowMode);
+        mCurrentDrawnInsets.set((int) (insets.left * mFullscreenProgress),
+                (int) (insets.top * mFullscreenProgress),
+                (int) (insets.right * mFullscreenProgress),
+                (int) (insets.bottom * mFullscreenProgress));
+        float fullscreenCornerRadius = isMultiWindowMode ? 0 : mWindowCornerRadius;
+        float cornerRadius = Utilities.mapRange(mFullscreenProgress, mCornerRadius,
+                fullscreenCornerRadius) / getRecentsView().getScaleX();
+
+        thumbnail.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius);
+        mOutlineProvider.setCurrentDrawnInsetsAndRadius(mCurrentDrawnInsets, cornerRadius);
+        invalidateOutline();
     }
 
     public boolean isRunningTask() {
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 95aea43..e84543b 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -18,6 +18,9 @@
 
     <string name="overview_callbacks_class" translatable="false"></string>
 
+    <!-- Activity which blocks home gesture -->
+    <string name="gesture_blocking_activity" translatable="false"></string>
+
     <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
 
     <string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index cda9d4f..e1a115a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -408,7 +408,7 @@
      */
     private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets,
             Rect windowTargetBounds, boolean toggleVisibility) {
-        Rect bounds = new Rect();
+        RectF bounds = new RectF();
         mFloatingView = FloatingIconView.getFloatingIconView(mLauncher, v, toggleVisibility,
                 bounds, true /* isOpening */, mFloatingView);
         Rect crop = new Rect();
@@ -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 / bounds.width();
+        float maxScaleY = smallestSize / 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/TestInformationProvider.java b/quickstep/src/com/android/quickstep/TestInformationProvider.java
index a948570..b37ddda 100644
--- a/quickstep/src/com/android/quickstep/TestInformationProvider.java
+++ b/quickstep/src/com/android/quickstep/TestInformationProvider.java
@@ -111,14 +111,6 @@
                     response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
                     break;
                 }
-
-                case TestProtocol.REQUEST_ENABLE_DRAG_LOGGING:
-                    TestProtocol.sDebugTracing = true;
-                    break;
-
-                case TestProtocol.REQUEST_DISABLE_DRAG_LOGGING:
-                    TestProtocol.sDebugTracing = false;
-                    break;
             }
             return response;
         }
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..1817135 100644
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
@@ -17,6 +17,7 @@
 package com.android.quickstep;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import android.app.prediction.AppPredictor;
 import android.app.prediction.AppTarget;
@@ -25,7 +26,9 @@
 import android.content.pm.LauncherActivityInfo;
 import android.os.Process;
 import android.view.View;
-import android.widget.ProgressBar;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
@@ -37,20 +40,16 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
 @LargeTest
 @RunWith(AndroidJUnit4.class)
-public class AppPredictionsUITests  extends AbstractQuickStepTest {
-    private static final int DEFAULT_APP_LAUNCH_TIMES = 3;
-    private static final String TAG = "AppPredictionsUITests";
+public class AppPredictionsUITests extends AbstractQuickStepTest {
 
     private LauncherActivityInfo mSampleApp1;
     private LauncherActivityInfo mSampleApp2;
@@ -86,24 +85,20 @@
      * Test that prediction UI is updated as soon as we get predictions from the system
      */
     @Test
+    @Ignore // b/131772711: this really fails (when being run as a part of the whole test suite)!
     public void testPredictionExistsInAllApps() {
         mActivityMonitor.startLauncher();
         mLauncher.pressHome().switchToAllApps();
 
-        // There has not been any update, verify that progress bar is showing
-        waitForLauncherCondition("Prediction is not in loading state", launcher -> {
-            ProgressBar p = findLoadingBar(launcher);
-            return p != null && p.isShown();
-        });
-
         // Dispatch an update
         sendPredictionUpdate(mSampleApp1, mSampleApp2);
+        // The first update should apply immediately.
         waitForLauncherCondition("Predictions were not updated in loading state",
                 launcher -> getPredictedApp(launcher).size() == 2);
     }
 
     /**
-     * Test tat prediction update is deferred if it is already visible
+     * Test that prediction update is deferred if it is already visible
      */
     @Test
     public void testPredictionsDeferredUntilHome() {
@@ -122,6 +117,20 @@
         assertEquals(3, getFromLauncher(this::getPredictedApp).size());
     }
 
+    @Test
+    @Ignore // b/131772711 - this was failing in the lab
+    public void testPredictionsDisabled() {
+        mActivityMonitor.startLauncher();
+        sendPredictionUpdate();
+        mLauncher.pressHome().switchToAllApps();
+
+        waitForLauncherCondition("Predictions were not updated in loading state",
+                launcher -> launcher.getAppsView().getFloatingHeaderView()
+                        .findFixedRowByType(PredictionRowView.class).getVisibility() == View.GONE);
+        assertFalse(PredictionUiStateManager.INSTANCE.get(mTargetContext)
+                .getCurrentState().isEnabled);
+    }
+
     public ArrayList<BubbleTextView> getPredictedApp(Launcher launcher) {
         PredictionRowView container = launcher.getAppsView().getFloatingHeaderView()
                 .findFixedRowByType(PredictionRowView.class);
@@ -136,20 +145,6 @@
         return predictedAppViews;
     }
 
-    private ProgressBar findLoadingBar(Launcher launcher) {
-        PredictionRowView container = launcher.getAppsView().getFloatingHeaderView()
-                .findFixedRowByType(PredictionRowView.class);
-
-        for (int i = 0; i < container.getChildCount(); i++) {
-            View view = container.getChildAt(i);
-            if (view instanceof ProgressBar) {
-                return (ProgressBar) view;
-            }
-        }
-        return null;
-    }
-
-
     private void sendPredictionUpdate(LauncherActivityInfo... activities) {
         getOnUiThread(() -> {
             List<AppTarget> targets = new ArrayList<>(activities.length);
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/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index e552f56..93e403c 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -71,7 +71,8 @@
 
     @Override
     public Statement apply(Statement base, Description description) {
-        if (TestHelpers.isInLauncherProcess() &&
+        // b/130558787; b/131419978
+        if (false && TestHelpers.isInLauncherProcess() &&
                 description.getAnnotation(NavigationModeSwitch.class) != null) {
             Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
             return new Statement() {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index b0ce5f5..4b6b3ee 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -64,7 +64,7 @@
     private void startTestApps() throws Exception {
         startAppFast(getAppPackageName());
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
-        startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS));
+        startTestActivity(2);
 
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
@@ -130,8 +130,8 @@
         OverviewTask task = mLauncher.pressHome().switchToOverview().getCurrentTask();
         assertNotNull("overview.getCurrentTask() returned null (1)", task);
         assertNotNull("OverviewTask.open returned null", task.open());
-        assertTrue("Contacts app didn't open from Overview", mDevice.wait(Until.hasObject(
-                By.pkg(resolveSystemApp(Intent.CATEGORY_APP_CONTACTS)).depth(0)),
+        assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
+                By.pkg(getAppPackageName()).text("TestActivity2")),
                 LONG_WAIT_TIME_MS));
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index c6fd906..ccd9e25 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.shortcuts.DeepShortcutManager;
 import com.android.launcher3.uioverrides.DisplayRotationListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
+import com.android.launcher3.util.Themes;
 
 import androidx.annotation.Nullable;
 
@@ -69,7 +70,7 @@
         // Update theme
         WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
         wallpaperColorInfo.addOnChangeListener(this);
-        int themeRes = getThemeRes(wallpaperColorInfo);
+        int themeRes = Themes.getActivityThemeRes(this);
         if (themeRes != mThemeRes) {
             mThemeRes = themeRes;
             setTheme(themeRes);
@@ -88,31 +89,11 @@
     }
 
     private void updateTheme() {
-        WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
-        if (mThemeRes != getThemeRes(wallpaperColorInfo)) {
+        if (mThemeRes != Themes.getActivityThemeRes(this)) {
             recreate();
         }
     }
 
-    protected int getThemeRes(WallpaperColorInfo wallpaperColorInfo) {
-        boolean darkTheme;
-        if (Utilities.ATLEAST_Q) {
-            Configuration configuration = getResources().getConfiguration();
-            int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
-            darkTheme = nightMode == Configuration.UI_MODE_NIGHT_YES;
-        } else {
-            darkTheme = wallpaperColorInfo.isDark();
-        }
-
-        if (darkTheme) {
-            return wallpaperColorInfo.supportsDarkText() ?
-                    R.style.AppTheme_Dark_DarkText : R.style.AppTheme_Dark;
-        } else {
-            return wallpaperColorInfo.supportsDarkText() ?
-                    R.style.AppTheme_DarkText : R.style.AppTheme;
-        }
-    }
-
     @Override
     public void onActionModeStarted(ActionMode mode) {
         super.onActionModeStarted(mode);
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index f300ef7..c84be4d 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -123,12 +123,12 @@
      * @param ev MotionEvent in {@param eventSource}
      */
     public boolean shouldContainerScroll(MotionEvent ev, View eventSource) {
-        int[] point = new int[2];
-        point[0] = (int) ev.getX();
-        point[1] = (int) ev.getY();
+        float[] point = new float[2];
+        point[0] = ev.getX();
+        point[1] = ev.getY();
         Utilities.mapCoordInSelfToDescendant(mScrollbar, eventSource, point);
         // IF the MotionEvent is inside the thumb, container should not be pulled down.
-        if (mScrollbar.shouldBlockIntercept(point[0], point[1])) {
+        if (mScrollbar.shouldBlockIntercept((int) point[0], (int) point[1])) {
             return false;
         }
 
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 3611ad4..8291acc 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -142,7 +142,6 @@
     public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         mActivity = ActivityContext.lookupContext(context);
-        DeviceProfile grid = mActivity.getDeviceProfile();
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
         TypedArray a = context.obtainStyledAttributes(attrs,
@@ -150,18 +149,24 @@
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
 
         int display = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
-        int defaultIconSize = grid.iconSizePx;
+        final int defaultIconSize;
         if (display == DISPLAY_WORKSPACE) {
+            DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
+            defaultIconSize = grid.iconSizePx;
         } else if (display == DISPLAY_ALL_APPS) {
+            DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
         } else if (display == DISPLAY_FOLDER) {
+            DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
+        } else {
+            defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
         }
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 9d9a639..3eb01e6 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -48,6 +48,9 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.annotation.IntDef;
+import androidx.core.view.ViewCompat;
+
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
 import com.android.launcher3.accessibility.FolderAccessibilityHelper;
@@ -57,12 +60,14 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.util.CellAndSpan;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.views.Transposable;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
 import java.lang.annotation.Retention;
@@ -73,10 +78,7 @@
 import java.util.Comparator;
 import java.util.Stack;
 
-import androidx.annotation.IntDef;
-import androidx.core.view.ViewCompat;
-
-public class CellLayout extends ViewGroup {
+public class CellLayout extends ViewGroup implements Transposable {
     public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
     public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
 
@@ -182,6 +184,7 @@
     // Related to accessible drag and drop
     private DragAndDropAccessibilityDelegate mTouchHelper;
     private boolean mUseTouchHelper = false;
+    private RotationMode mRotationMode = RotationMode.NORMAL;
 
     public CellLayout(Context context) {
         this(context, null);
@@ -203,7 +206,7 @@
         setClipToPadding(false);
         mActivity = ActivityContext.lookupContext(context);
 
-        DeviceProfile grid = mActivity.getDeviceProfile();
+        DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
 
         mCellWidth = mCellHeight = -1;
         mFixedCellWidth = mFixedCellHeight = -1;
@@ -317,6 +320,24 @@
         }
     }
 
+    public void setRotationMode(RotationMode mode) {
+        if (mRotationMode != mode) {
+            mRotationMode = mode;
+            requestLayout();
+        }
+    }
+
+    @Override
+    public RotationMode getRotationMode() {
+        return mRotationMode;
+    }
+
+    @Override
+    public void setPadding(int left, int top, int right, int bottom) {
+        mRotationMode.mapRect(left, top, right, bottom, mTempRect);
+        super.setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom);
+    }
+
     @Override
     public boolean dispatchHoverEvent(MotionEvent event) {
         // Always attempt to dispatch hover events to accessibility first.
@@ -339,6 +360,10 @@
         mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
     }
 
+    public boolean isHardwareLayerEnabled() {
+        return mShortcutsAndWidgets.getLayerType() == LAYER_TYPE_HARDWARE;
+    }
+
     public void setCellDimensions(int width, int height) {
         mFixedCellWidth = mCellWidth = width;
         mFixedCellHeight = mCellHeight = height;
@@ -745,6 +770,14 @@
         int heightSize =  MeasureSpec.getSize(heightMeasureSpec);
         int childWidthSize = widthSize - (getPaddingLeft() + getPaddingRight());
         int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
+
+        mShortcutsAndWidgets.setRotation(mRotationMode.surfaceRotation);
+        if (mRotationMode.isTransposed) {
+            int tmp = childWidthSize;
+            childWidthSize = childHeightSize;
+            childHeightSize = tmp;
+        }
+
         if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
             int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
             int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
@@ -787,7 +820,6 @@
         int top = getPaddingTop();
         int bottom = b - t - getPaddingBottom();
 
-        mShortcutsAndWidgets.layout(left, top, right, bottom);
         // Expand the background drawing bounds by the padding baked into the background drawable
         mBackground.getPadding(mTempRect);
         mBackground.setBounds(
@@ -795,6 +827,16 @@
                 top - mTempRect.top - getPaddingTop(),
                 right + mTempRect.right + getPaddingRight(),
                 bottom + mTempRect.bottom + getPaddingBottom());
+
+        if (mRotationMode.isTransposed) {
+            int halfW = mShortcutsAndWidgets.getMeasuredWidth() / 2;
+            int halfH = mShortcutsAndWidgets.getMeasuredHeight() / 2;
+            int cX = (left + right) / 2;
+            int cY = (top + bottom) / 2;
+            mShortcutsAndWidgets.layout(cX - halfW, cY - halfH, cX + halfW, cY + halfH);
+        } else {
+            mShortcutsAndWidgets.layout(left, top, right, bottom);
+        }
     }
 
     /**
@@ -929,7 +971,7 @@
             if (resize) {
                 cellToRect(cellX, cellY, spanX, spanY, r);
                 if (v instanceof LauncherAppWidgetHostView) {
-                    DeviceProfile profile = mActivity.getDeviceProfile();
+                    DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
                     Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
                 }
             } else {
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index b86e7c0..639c173 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -33,20 +33,12 @@
 
     class CheckForLongPress implements Runnable {
         public void run() {
-            if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                        "CheckForLongPress1");
-            }
             if ((mView.getParent() != null) && mView.hasWindowFocus()
                     && !mHasPerformedLongPress) {
                 boolean handled;
                 if (mListener != null) {
                     handled = mListener.onLongClick(mView);
                 } else {
-                    if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                        android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                                "CheckForLongPress2");
-                    }
                     handled = mView.performLongClick();
                 }
                 if (handled) {
@@ -81,21 +73,11 @@
         }
         mView.postDelayed(mPendingCheckForLongPress,
                 (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "postCheckForLongPress: " + ViewConfiguration.getLongPressTimeout() + " "
-                            + mLongPressTimeoutFactor);
-        }
     }
 
     public void cancelLongPress() {
         mHasPerformedLongPress = false;
         if (mPendingCheckForLongPress != null) {
-            if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                        "cancelLongPress @ " + android.util.Log.getStackTraceString(
-                                new Throwable()));
-            }
             mView.removeCallbacks(mPendingCheckForLongPress);
             mPendingCheckForLongPress = null;
         }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 6a3a26f..c0affb9 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -419,6 +419,10 @@
         updateWorkspacePadding();
     }
 
+    /**
+     * The current device insets. This is generally same as the insets being dispatched to
+     * {@link Insettable} elements, but can differ if the element is using a different profile.
+     */
     public Rect getInsets() {
         return mInsets;
     }
@@ -582,45 +586,6 @@
         }
     }
 
-    /**
-     * Gets an item's location on the home screen. This is useful if the home screen
-     * is animating, otherwise use {@link View#getLocationOnScreen(int[])}.
-     * @param pageDiff The page difference relative to the current page.
-     */
-    public void getItemLocation(int cellX, int cellY, int spanX, int spanY, int container,
-            int pageDiff, Rect outBounds) {
-        outBounds.setEmpty();
-        if (container == CONTAINER_HOTSEAT) {
-            final int actualHotseatCellHeight;
-            if (isVerticalBarLayout()) {
-                actualHotseatCellHeight = availableHeightPx / inv.numRows;
-                if (mIsSeascape) {
-                    outBounds.left = mHotseatPadding.left;
-                } else {
-                    outBounds.left = availableWidthPx - hotseatBarSizePx + mHotseatPadding.left;
-                }
-                outBounds.right = outBounds.left + iconSizePx;
-                outBounds.top = mHotseatPadding.top
-                        + actualHotseatCellHeight * (inv.numRows - cellX - 1);
-                outBounds.bottom = outBounds.top + actualHotseatCellHeight;
-            } else {
-                actualHotseatCellHeight = hotseatBarSizePx - hotseatBarBottomPaddingPx
-                        - hotseatBarTopPaddingPx;
-                outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
-                        + (cellX * getCellSize().x);
-                outBounds.right = outBounds.left + getCellSize().x;
-                outBounds.top = mInsets.top + availableHeightPx - hotseatBarSizePx;
-                outBounds.bottom = outBounds.top + actualHotseatCellHeight;
-            }
-        } else {
-            outBounds.left = mInsets.left + workspacePadding.left + cellLayoutPaddingLeftRightPx
-                    + (cellX * getCellSize().x) + (pageDiff * availableWidthPx);
-            outBounds.right = outBounds.left + (getCellSize().x * spanX);
-            outBounds.top = mInsets.top + workspacePadding.top + (cellY * getCellSize().y);
-            outBounds.bottom = outBounds.top + (getCellSize().y * spanY);
-        }
-    }
-
     private static Context getContext(Context c, int orientation) {
         Configuration context = new Configuration(c.getResources().getConfiguration());
         context.orientation = orientation;
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 0543e8d..ca001a3 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -204,7 +204,7 @@
         return visibleCount;
     }
 
-    private void animateToVisibility(boolean isVisible) {
+    public void animateToVisibility(boolean isVisible) {
         if (mVisible != isVisible) {
             mVisible = isVisible;
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 4da7907..00acdcd 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -26,11 +26,13 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.views.Transposable;
 
-public class Hotseat extends CellLayout implements LogContainerProvider, Insettable {
+public class Hotseat extends CellLayout implements LogContainerProvider, Insettable, Transposable {
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
@@ -77,7 +79,8 @@
     @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        DeviceProfile grid = mActivity.getDeviceProfile();
+        DeviceProfile grid = mActivity.getWallpaperDeviceProfile();
+        insets = grid.getInsets();
 
         if (grid.isVerticalBarLayout()) {
             lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -105,4 +108,9 @@
         // Don't let if follow through to workspace
         return true;
     }
+
+    @Override
+    public RotationMode getRotationMode() {
+        return Launcher.getLauncher(getContext()).getRotationMode();
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index fda674f..417c5a2 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -53,6 +53,7 @@
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -71,6 +72,7 @@
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
@@ -93,6 +95,7 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderIconPreviewVerifier;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
@@ -272,6 +275,9 @@
 
     private float mCurrentAssistantVisibility = 0f;
 
+    private DeviceProfile mStableDeviceProfile;
+    private RotationMode mRotationMode = RotationMode.NORMAL;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER);
@@ -390,6 +396,12 @@
                 }
             }
         });
+
+        if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
+            WindowManager.LayoutParams lp = getWindow().getAttributes();
+            lp.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+            getWindow().setAttributes(lp);
+        }
     }
 
     @Override
@@ -418,6 +430,10 @@
 
     @Override
     protected void reapplyUi() {
+        if (FeatureFlags.FAKE_LANDSCAPE_UI.get()) {
+            mRotationMode = mStableDeviceProfile == null ? RotationMode.NORMAL :
+                    (mDeviceProfile.isSeascape() ? RotationMode.SEASCAPE : RotationMode.LANDSCAPE);
+        }
         getRootView().dispatchInsets();
         getStateManager().reapplyState(true /* cancelCurrentAnimation */);
     }
@@ -469,8 +485,41 @@
             display.getSize(mwSize);
             mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
         }
+
+        if (FeatureFlags.FAKE_LANDSCAPE_UI.get() && mDeviceProfile.isVerticalBarLayout()
+                && !mDeviceProfile.isMultiWindowMode) {
+            mStableDeviceProfile = mDeviceProfile.inv.portraitProfile;
+            mRotationMode = mDeviceProfile.isSeascape()
+                    ? RotationMode.SEASCAPE : RotationMode.LANDSCAPE;
+        } else {
+            mStableDeviceProfile = null;
+            mRotationMode = RotationMode.NORMAL;
+        }
+
         onDeviceProfileInitiated();
-        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout(), true);
+        mModelWriter = mModel.getWriter(getWallpaperDeviceProfile().isVerticalBarLayout(), true);
+    }
+
+    public void updateInsets(Rect insets) {
+        mDeviceProfile.updateInsets(insets);
+        if (mStableDeviceProfile != null) {
+            mStableDeviceProfile.updateInsets(insets);
+        }
+    }
+
+    @Override
+    public RotationMode getRotationMode() {
+        return mRotationMode;
+    }
+
+    /**
+     * Device profile to be used by UI elements which are shown directly on top of the wallpaper
+     * and whose presentation is tied to the wallpaper (and physical device) and not the activity
+     * configuration.
+     */
+    @Override
+    public DeviceProfile getWallpaperDeviceProfile() {
+        return mStableDeviceProfile == null ? mDeviceProfile : mStableDeviceProfile;
     }
 
     public RotationHelper getRotationHelper() {
@@ -879,7 +928,7 @@
         super.onPause();
         mDragController.cancelDrag();
         mDragController.resetLastGestureUpTime();
-
+        mDropTargetBar.animateToVisibility(false);
         if (mLauncherCallbacks != null) {
             mLauncherCallbacks.onPause();
         }
@@ -1812,7 +1861,7 @@
         mAppWidgetHost.clearViews();
 
         if (mHotseat != null) {
-            mHotseat.resetLayout(mDeviceProfile.isVerticalBarLayout());
+            mHotseat.resetLayout(getWallpaperDeviceProfile().isVerticalBarLayout());
         }
         TraceHelper.endSection("startBinding");
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e788ceb..e7b4ff4 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -213,7 +213,6 @@
         synchronized (mLock) {
             Preconditions.assertUIThread();
             mCallbacks = new WeakReference<>(callbacks);
-            android.util.Log.d("b/131170582", "mCallbacks = " + mCallbacks);
         }
     }
 
@@ -331,7 +330,6 @@
             // Stop any existing loaders first, so they don't set mModelLoaded to true later
             stopLoader();
             mModelLoaded = false;
-            android.util.Log.d("b/131170582", "1 mModelLoaded = " + mModelLoaded);
         }
 
         // Start the loader if launcher is already running, otherwise the loader will run,
@@ -392,7 +390,6 @@
         synchronized (mLock) {
             LoaderTask oldTask = mLoaderTask;
             mLoaderTask = null;
-            android.util.Log.d("b/131170582", "1 mLoaderTask = " + mLoaderTask);
             if (oldTask != null) {
                 oldTask.stopLocked();
             }
@@ -403,7 +400,6 @@
         synchronized (mLock) {
             stopLoader();
             mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
-            android.util.Log.d("b/131170582", "2 mLoaderTask = " + mLoaderTask);
             runOnWorkerThread(mLoaderTask);
         }
     }
@@ -448,7 +444,6 @@
                 mTask = task;
                 mIsLoaderTaskRunning = true;
                 mModelLoaded = false;
-                android.util.Log.d("b/131170582", "2 mModelLoaded = " + mModelLoaded);
             }
         }
 
@@ -456,7 +451,6 @@
             synchronized (mLock) {
                 // Everything loaded bind the data.
                 mModelLoaded = true;
-                android.util.Log.d("b/131170582", "3 mModelLoaded = " + mModelLoaded);
             }
         }
 
@@ -466,7 +460,6 @@
                 // If we are still the last one to be scheduled, remove ourselves.
                 if (mLoaderTask == mTask) {
                     mLoaderTask = null;
-                    android.util.Log.d("b/131170582", "3 mLoaderTask = " + mLoaderTask);
                 }
                 mIsLoaderTaskRunning = false;
             }
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index e6c2d0c..20eec05 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -8,13 +8,10 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Insets;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.WindowInsets;
@@ -32,9 +29,6 @@
     private final Rect mConsumedInsets = new Rect();
 
     @ViewDebug.ExportedProperty(category = "launcher")
-    private final RectF mTouchExcludeRegion = new RectF();
-
-    @ViewDebug.ExportedProperty(category = "launcher")
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
 
@@ -87,7 +81,7 @@
                 UI_STATE_ROOT_VIEW, drawInsetBar ? FLAG_DARK_NAV : 0);
 
         // Update device profile before notifying th children.
-        mLauncher.getDeviceProfile().updateInsets(insets);
+        mLauncher.updateInsets(insets);
         boolean resetState = !insets.equals(mInsets);
         setInsets(insets);
 
@@ -120,7 +114,7 @@
     }
 
     public void dispatchInsets() {
-        mLauncher.getDeviceProfile().updateInsets(mInsets);
+        mLauncher.updateInsets(mInsets);
         super.setInsets(mInsets);
     }
 
@@ -164,30 +158,11 @@
 
     @Override
     public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
-        if (Utilities.ATLEAST_Q) {
-            Insets gestureInsets = insets.getMandatorySystemGestureInsets();
-            mTouchExcludeRegion.set(gestureInsets.left, gestureInsets.top,
-                    gestureInsets.right, gestureInsets.bottom);
-        }
+        mLauncher.getDragLayer().updateTouchExcludeRegion(insets);
         return super.dispatchApplyWindowInsets(insets);
     }
 
     @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            float x = ev.getX();
-            float y = ev.getY();
-            if (y < mTouchExcludeRegion.top
-                    || x < mTouchExcludeRegion.left
-                    || x > (getWidth() - mTouchExcludeRegion.right)
-                    || y > (getHeight() - mTouchExcludeRegion.bottom)) {
-                return false;
-            }
-        }
-        return super.dispatchTouchEvent(ev);
-    }
-
-    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(l, t, r, b);
@@ -211,14 +186,4 @@
 
         void onWindowVisibilityChanged(int visibility);
     }
-
-    @Override
-    public void requestLayout() {
-        super.requestLayout();
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "requestLayout @ " + android.util.Log.getStackTraceString(
-                            new Throwable()));
-        }
-    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 3a02b07..b640430 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1088,10 +1088,8 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        super.onTouchEvent(ev);
-
         // Skip touch handling if there are no pages to swipe
-        if (getChildCount() <= 0) return super.onTouchEvent(ev);
+        if (getChildCount() <= 0) return false;
 
         acquireVelocityTrackerAndAddMovement(ev);
 
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 30f418d..1bd8263 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -93,7 +93,7 @@
     public void setupLp(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         if (child instanceof LauncherAppWidgetHostView) {
-            DeviceProfile profile = mActivity.getDeviceProfile();
+            DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
                     profile.appWidgetScale.x, profile.appWidgetScale.y);
         } else {
@@ -108,12 +108,12 @@
 
     public int getCellContentHeight() {
         return Math.min(getMeasuredHeight(),
-                mActivity.getDeviceProfile().getCellHeight(mContainerType));
+                mActivity.getWallpaperDeviceProfile().getCellHeight(mContainerType));
     }
 
     public void measureChild(View child) {
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
-        final DeviceProfile profile = mActivity.getDeviceProfile();
+        final DeviceProfile profile = mActivity.getWallpaperDeviceProfile();
 
         if (child instanceof LauncherAppWidgetHostView) {
             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
diff --git a/src/com/android/launcher3/TestProtocol.java b/src/com/android/launcher3/TestProtocol.java
index dab4282..ecbaa5b 100644
--- a/src/com/android/launcher3/TestProtocol.java
+++ b/src/com/android/launcher3/TestProtocol.java
@@ -63,9 +63,4 @@
             "all-apps-to-overview-swipe-height";
     public static final String REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT =
             "home-to-all-apps-swipe-height";
-
-    public static boolean sDebugTracing = false;
-    public static final String NO_DRAG_TAG = "b/129434166";
-    public static final String REQUEST_ENABLE_DRAG_LOGGING = "enable-drag-logging";
-    public static final String REQUEST_DISABLE_DRAG_LOGGING = "disable-drag-logging";
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 382eedd..26364be 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,9 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
-
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.app.WallpaperManager;
@@ -63,13 +60,12 @@
 import com.android.launcher3.compat.LauncherAppsCompat;
 import com.android.launcher3.compat.ShortcutConfigActivityInfo;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
-import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.shortcuts.DeepShortcutManager;
-import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.views.Transposable;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.io.Closeable;
@@ -97,7 +93,6 @@
 
     private static final int[] sLoc0 = new int[2];
     private static final int[] sLoc1 = new int[2];
-    private static final float[] sPoint = new float[2];
     private static final Matrix sMatrix = new Matrix();
     private static final Matrix sInverseMatrix = new Matrix();
 
@@ -143,7 +138,7 @@
      */
     public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
             CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
-            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
+            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
 
     public static boolean IS_RUNNING_IN_TEST_HARNESS =
                     ActivityManager.isRunningInTestHarness();
@@ -170,37 +165,60 @@
      *         assumption fails, we will need to return a pair of scale factors.
      */
     public static float getDescendantCoordRelativeToAncestor(
-            View descendant, View ancestor, int[] coord, boolean includeRootScroll) {
-        sPoint[0] = coord[0];
-        sPoint[1] = coord[1];
+            View descendant, View ancestor, float[] coord, boolean includeRootScroll) {
+        return getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, includeRootScroll,
+                false);
+    }
 
+    /**
+     * Given a coordinate relative to the descendant, find the coordinate in a parent view's
+     * coordinates.
+     *
+     * @param descendant The descendant to which the passed coordinate is relative.
+     * @param ancestor The root view to make the coordinates relative to.
+     * @param coord The coordinate that we want mapped.
+     * @param includeRootScroll Whether or not to account for the scroll of the descendant:
+     *          sometimes this is relevant as in a child's coordinates within the descendant.
+     * @param ignoreTransform If true, view transform is ignored
+     * @return The factor by which this descendant is scaled relative to this DragLayer. Caution
+     *         this scale factor is assumed to be equal in X and Y, and so if at any point this
+     *         assumption fails, we will need to return a pair of scale factors.
+     */
+    public static float getDescendantCoordRelativeToAncestor(View descendant, View ancestor,
+            float[] coord, boolean includeRootScroll, boolean ignoreTransform) {
         float scale = 1.0f;
         View v = descendant;
         while(v != ancestor && v != null) {
             // For TextViews, scroll has a meaning which relates to the text position
             // which is very strange... ignore the scroll.
             if (v != descendant || includeRootScroll) {
-                sPoint[0] -= v.getScrollX();
-                sPoint[1] -= v.getScrollY();
+                offsetPoints(coord, -v.getScrollX(), -v.getScrollY());
             }
 
-            v.getMatrix().mapPoints(sPoint);
-            sPoint[0] += v.getLeft();
-            sPoint[1] += v.getTop();
+            if (ignoreTransform) {
+                if (v instanceof Transposable) {
+                    RotationMode m = ((Transposable) v).getRotationMode();
+                    if (m.isTransposed) {
+                        sMatrix.setRotate(m.surfaceRotation, v.getPivotX(), v.getPivotY());
+                        sMatrix.mapPoints(coord);
+                    }
+                }
+            } else {
+                v.getMatrix().mapPoints(coord);
+            }
+            offsetPoints(coord, v.getLeft(), v.getTop());
             scale *= v.getScaleX();
 
             v = (View) v.getParent();
         }
-
-        coord[0] = Math.round(sPoint[0]);
-        coord[1] = Math.round(sPoint[1]);
         return scale;
     }
 
+
     /**
-     * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, int[], boolean)}.
+     * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}.
      */
-    public static void mapCoordInSelfToDescendant(View descendant, View root, int[] coord) {
+    public static void mapCoordInSelfToDescendant(View descendant, View root, float[] coord) {
         sMatrix.reset();
         View v = descendant;
         while(v != root) {
@@ -211,12 +229,23 @@
         }
         sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY());
         sMatrix.invert(sInverseMatrix);
+        sInverseMatrix.mapPoints(coord);
+    }
 
-        sPoint[0] = coord[0];
-        sPoint[1] = coord[1];
-        sInverseMatrix.mapPoints(sPoint);
-        coord[0] = Math.round(sPoint[0]);
-        coord[1] = Math.round(sPoint[1]);
+    /**
+     * Sets {@param out} to be same as {@param in} by rounding individual values
+     */
+    public static void roundArray(float[] in, int[] out) {
+       for (int i = 0; i < in.length; i++) {
+           out[i] = Math.round(in[i]);
+       }
+    }
+
+    public static void offsetPoints(float[] points, float offsetX, float offsetY) {
+        for (int i = 0; i < points.length; i += 2) {
+            points[i] += offsetX;
+            points[i + 1] += offsetY;
+        }
     }
 
     /**
@@ -569,53 +598,6 @@
         return String.format(Locale.ENGLISH, "%d,%d", x, y);
     }
 
-    /**
-     * Returns the location bounds of a view.
-     * - For DeepShortcutView, we return the bounds of the icon view.
-     * - For BubbleTextView, we return the icon bounds.
-     */
-    public static void getLocationBoundsForView(Launcher launcher, View v, Rect outRect) {
-        final DragLayer dragLayer = launcher.getDragLayer();
-        final boolean isBubbleTextView = v instanceof BubbleTextView;
-        final boolean isFolderIcon = v instanceof FolderIcon;
-        final Rect rect = new Rect();
-
-        // Deep shortcut views have their icon drawn in a separate view.
-        final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
-        if (v instanceof DeepShortcutView) {
-            dragLayer.getDescendantRectRelativeToSelf(((DeepShortcutView) v).getIconView(), rect);
-        } else if (fromDeepShortcutView) {
-            DeepShortcutView view = (DeepShortcutView) v.getParent();
-            dragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect);
-        } else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
-                && (((ItemInfo) v.getTag()).container == CONTAINER_DESKTOP
-                || ((ItemInfo) v.getTag()).container == CONTAINER_HOTSEAT)) {
-            CellLayout pageViewIsOn = ((CellLayout) v.getParent().getParent());
-            int pageNum = launcher.getWorkspace().indexOfChild(pageViewIsOn);
-
-            DeviceProfile dp = launcher.getDeviceProfile();
-            ItemInfo info = ((ItemInfo) v.getTag());
-            dp.getItemLocation(info.cellX, info.cellY, info.spanX, info.spanY,
-                    info.container, pageNum - launcher.getCurrentWorkspaceScreen(), rect);
-        } else {
-            dragLayer.getDescendantRectRelativeToSelf(v, rect);
-        }
-        int viewLocationLeft = rect.left;
-        int viewLocationTop = rect.top;
-
-        if (isBubbleTextView && !fromDeepShortcutView) {
-            ((BubbleTextView) v).getIconBounds(rect);
-        } else if (isFolderIcon) {
-            ((FolderIcon) v).getPreviewBounds(rect);
-        } else {
-            rect.set(0, 0, rect.width(), rect.height());
-        }
-        viewLocationLeft += rect.left;
-        viewLocationTop += rect.top;
-        outRect.set(viewLocationLeft, viewLocationTop, viewLocationLeft + rect.width(),
-                viewLocationTop + rect.height());
-    }
-
     public static void unregisterReceiverSafely(Context context, BroadcastReceiver receiver) {
         try {
             context.unregisterReceiver(receiver);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 8849768..0f4c42d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -82,6 +82,7 @@
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
@@ -175,7 +176,9 @@
     @Thunk final Launcher mLauncher;
     @Thunk DragController mDragController;
 
+    private final Rect mTempRect = new Rect();
     private final int[] mTempXY = new int[2];
+    private final float[] mTempFXY = new float[2];
     @Thunk float[] mDragViewVisualCenter = new float[2];
     private final float[] mTempTouchCoordinates = new float[2];
 
@@ -285,18 +288,23 @@
 
     @Override
     public void setInsets(Rect insets) {
-        mInsets.set(insets);
-
         DeviceProfile grid = mLauncher.getDeviceProfile();
-        mMaxDistanceForFolderCreation = grid.isTablet
-                ? 0.75f * grid.iconSizePx
-                : 0.55f * grid.iconSizePx;
+        DeviceProfile stableGrid = mLauncher.getWallpaperDeviceProfile();
+
+        mMaxDistanceForFolderCreation = stableGrid.isTablet
+                ? 0.75f * stableGrid.iconSizePx
+                : 0.55f * stableGrid.iconSizePx;
         mWorkspaceFadeInAdjacentScreens = grid.shouldFadeAdjacentWorkspaceScreens();
 
-        Rect padding = grid.workspacePadding;
-        setPadding(padding.left, padding.top, padding.right, padding.bottom);
+        Rect padding = stableGrid.workspacePadding;
 
-        if (grid.shouldFadeAdjacentWorkspaceScreens()) {
+        RotationMode rotationMode = mLauncher.getRotationMode();
+
+        rotationMode.mapRect(padding, mTempRect);
+        setPadding(mTempRect.left, mTempRect.top, mTempRect.right, mTempRect.bottom);
+        rotationMode.mapRect(insets, mInsets);
+
+        if (mWorkspaceFadeInAdjacentScreens) {
             // In landscape mode the page spacing is set to the default.
             setPageSpacing(grid.edgeMarginPx);
         } else {
@@ -306,11 +314,13 @@
             setPageSpacing(Math.max(grid.edgeMarginPx, padding.left + 1));
         }
 
-        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
-        int paddingBottom = grid.cellLayoutBottomPaddingPx;
+
+        int paddingLeftRight = stableGrid.cellLayoutPaddingLeftRightPx;
+        int paddingBottom = stableGrid.cellLayoutBottomPaddingPx;
         for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
-            mWorkspaceScreens.valueAt(i)
-                    .setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
+            CellLayout page = mWorkspaceScreens.valueAt(i);
+            page.setRotationMode(rotationMode);
+            page.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
         }
     }
 
@@ -330,7 +340,7 @@
 
             float scale = 1;
             if (isWidget) {
-                DeviceProfile profile = mLauncher.getDeviceProfile();
+                DeviceProfile profile = mLauncher.getWallpaperDeviceProfile();
                 scale = Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
             }
             size[0] = r.width();
@@ -550,8 +560,10 @@
         // created CellLayout.
         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                         R.layout.workspace_screen, this, false /* attachToRoot */);
-        int paddingLeftRight = mLauncher.getDeviceProfile().cellLayoutPaddingLeftRightPx;
-        int paddingBottom = mLauncher.getDeviceProfile().cellLayoutBottomPaddingPx;
+        DeviceProfile grid = mLauncher.getWallpaperDeviceProfile();
+        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
+        int paddingBottom = grid.cellLayoutBottomPaddingPx;
+        newScreen.setRotationMode(mLauncher.getRotationMode());
         newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
 
         mWorkspaceScreens.put(screenId, newScreen);
@@ -1440,10 +1452,6 @@
 
     public DragView beginDragShared(View child, DragSource source, ItemInfo dragObject,
             DragPreviewProvider previewProvider, DragOptions dragOptions) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "beginDragShared");
-        }
         float iconScale = 1f;
         if (child instanceof BubbleTextView) {
             Drawable icon = ((BubbleTextView) child).getIcon();
@@ -1934,18 +1942,9 @@
         if (child == null) {
             return;
         }
+
         ShortcutAndWidgetContainer boundingLayout = child.getShortcutsAndWidgets();
-
-        // Use the absolute left instead of the child left, as we want the visible area
-        // irrespective of the visible child. Since the view can only scroll horizontally, the
-        // top position is not affected.
-        mTempXY[0] = getPaddingLeft() + boundingLayout.getLeft();
-        mTempXY[1] = child.getTop() + boundingLayout.getTop();
-
-        float scale = mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY);
-        outArea.set(mTempXY[0], mTempXY[1],
-                (int) (mTempXY[0] + scale * boundingLayout.getMeasuredWidth()),
-                (int) (mTempXY[1] + scale * boundingLayout.getMeasuredHeight()));
+        mLauncher.getDragLayer().getDescendantRectRelativeToSelf(boundingLayout, outArea);
     }
 
     @Override
@@ -2098,14 +2097,14 @@
    }
 
    boolean isPointInSelfOverHotseat(int x, int y) {
-       mTempXY[0] = x;
-       mTempXY[1] = y;
-       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
+       mTempFXY[0] = x;
+       mTempFXY[1] = y;
+       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
        View hotseat = mLauncher.getHotseat();
-       return mTempXY[0] >= hotseat.getLeft() &&
-               mTempXY[0] <= hotseat.getRight() &&
-               mTempXY[1] >= hotseat.getTop() &&
-               mTempXY[1] <= hotseat.getBottom();
+       return mTempFXY[0] >= hotseat.getLeft() &&
+               mTempFXY[0] <= hotseat.getRight() &&
+               mTempFXY[1] >= hotseat.getTop() &&
+               mTempFXY[1] <= hotseat.getBottom();
    }
 
     /**
@@ -2115,13 +2114,8 @@
      */
    private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
        if (mLauncher.isHotseatLayout(layout)) {
-           mTempXY[0] = (int) xy[0];
-           mTempXY[1] = (int) xy[1];
-           mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempXY, true);
-           mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, mTempXY);
-
-           xy[0] = mTempXY[0];
-           xy[1] = mTempXY[1];
+           mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
+           mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
        } else {
            mapPointFromSelfToChild(layout, xy);
        }
@@ -2612,13 +2606,14 @@
             DeviceProfile profile = mLauncher.getDeviceProfile();
             Utilities.shrinkRect(r, profile.appWidgetScale.x, profile.appWidgetScale.y);
         }
-        loc[0] = r.left;
-        loc[1] = r.top;
 
+        mTempFXY[0] = r.left;
+        mTempFXY[1] = r.top;
         setFinalTransitionTransform();
         float cellLayoutScale =
-                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, loc, true);
+                mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(layout, mTempFXY, true);
         resetTransitionTransform();
+        Utilities.roundArray(mTempFXY, loc);
 
         if (scale) {
             float dragViewScaleX = (1.0f * r.width()) / dragView.getMeasuredWidth();
@@ -2911,14 +2906,11 @@
                 && info.user.equals(user);
         final Workspace.ItemOperator packageAndUserAndApp = (ItemInfo info, View view) ->
                 packageAndUser.evaluate(info, view) && info.itemType == ITEM_TYPE_APPLICATION;
-        final Workspace.ItemOperator packageAndUserAndShortcut = (ItemInfo info, View view) ->
-                packageAndUser.evaluate(info, view) && (info.itemType == ITEM_TYPE_SHORTCUT
-                        || info.itemType == ITEM_TYPE_DEEP_SHORTCUT);
-        final Workspace.ItemOperator packageAndUserInFolder = (info, view) -> {
+        final Workspace.ItemOperator packageAndUserAndAppInFolder = (info, view) -> {
             if (info instanceof FolderInfo) {
                 FolderInfo folderInfo = (FolderInfo) info;
                 for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
-                    if (packageAndUser.evaluate(shortcutInfo, view)) {
+                    if (packageAndUserAndApp.evaluate(shortcutInfo, view)) {
                         return true;
                     }
                 }
@@ -2926,15 +2918,15 @@
             return false;
         };
 
-        // Order: App icons, shortcuts, app/shortcut in folder. Items in hotseat get returned first.
+        // Order: App icons, app in folder. Items in hotseat get returned first.
         if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
             return getFirstMatch(new CellLayout[] { getHotseat(), currentPage },
-                    packageAndUserAndApp, packageAndUserAndShortcut, packageAndUserInFolder);
+                    packageAndUserAndApp, packageAndUserAndAppInFolder);
         } else {
             // Do not use Folder as a criteria, since it'll cause a crash when trying to draw
             // FolderAdaptiveIcon as the background.
             return getFirstMatch(new CellLayout[] { getHotseat(), currentPage },
-                    packageAndUserAndApp, packageAndUserAndShortcut);
+                    packageAndUserAndApp);
         }
     }
 
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 99a8801..8d0259d 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -93,14 +93,16 @@
             Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
 
-            // Set the hotseat's pivot point to match the workspace's, so that it scales together.
-            DragLayer dragLayer = mLauncher.getDragLayer();
-            int[] workspacePivot = new int[]{(int) mWorkspace.getPivotX(),
-                    (int) mWorkspace.getPivotY()};
-            dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
-            dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
-            hotseat.setPivotX(workspacePivot[0]);
-            hotseat.setPivotY(workspacePivot[1]);
+            if (!hotseat.getRotationMode().isTransposed) {
+                // Set the hotseat's pivot point to match the workspace's, so that it scales together.
+                DragLayer dragLayer = mLauncher.getDragLayer();
+                float[] workspacePivot =
+                        new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() };
+                dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
+                dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
+                hotseat.setPivotX(workspacePivot[0]);
+                hotseat.setPivotY(workspacePivot[1]);
+            }
             float hotseatScale = hotseatScaleAndTranslation.scale;
             propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale, scaleInterpolator);
 
diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java
index 3d21d82..45d49e8 100644
--- a/src/com/android/launcher3/anim/FlingSpringAnim.java
+++ b/src/com/android/launcher3/anim/FlingSpringAnim.java
@@ -34,6 +34,7 @@
     private static final float SPRING_DAMPING = SpringForce.DAMPING_RATIO_LOW_BOUNCY;
 
     private final FlingAnimation mFlingAnim;
+    private SpringAnimation mSpringAnim;
 
     public <K> FlingSpringAnim(K object, FloatPropertyCompat<K> property, float startPosition,
             float targetPosition, float startVelocity, OnAnimationEndListener onEndListener) {
@@ -44,17 +45,24 @@
                 .setMinValue(Math.min(startPosition, targetPosition))
                 .setMaxValue(Math.max(startPosition, targetPosition));
         mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> {
-            SpringAnimation springAnim = new SpringAnimation(object, property)
+            mSpringAnim = new SpringAnimation(object, property)
                     .setStartVelocity(velocity)
                     .setSpring(new SpringForce(targetPosition)
                             .setStiffness(SPRING_STIFFNESS)
                             .setDampingRatio(SPRING_DAMPING));
-            springAnim.addEndListener(onEndListener);
-            springAnim.start();
+            mSpringAnim.addEndListener(onEndListener);
+            mSpringAnim.start();
         }));
     }
 
     public void start() {
         mFlingAnim.start();
     }
+
+    public void end() {
+        mFlingAnim.cancel();
+        if (mSpringAnim.canSkipToEnd()) {
+            mSpringAnim.skipToEnd();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index a55ea82..70df97a 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -110,6 +110,10 @@
             "ENABLE_HINTS_IN_OVERVIEW", false,
             "Show chip hints and gleams on the overview screen");
 
+    public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag(
+            "FAKE_LANDSCAPE_UI", false,
+            "Rotate launcher UI instead of using transposed layout");
+
     public static void initialize(Context context) {
         // Avoid the disk read for user builds
         if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 7210759..f92e00a 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -219,9 +219,6 @@
     }
 
     private void callOnDragStart() {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG, "callOnDragStart");
-        }
         if (mOptions.preDragCondition != null) {
             mOptions.preDragCondition.onPreDragEnd(mDragObject, true /* dragStarted*/);
         }
@@ -475,9 +472,6 @@
     }
 
     private void handleMoveEvent(int x, int y) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG, "handleMoveEvent1");
-        }
         mDragObject.dragView.move(x, y);
 
         // Drop on someone?
@@ -494,10 +488,6 @@
 
         if (mIsInPreDrag && mOptions.preDragCondition != null
                 && mOptions.preDragCondition.shouldStartDrag(mDistanceSinceScroll)) {
-            if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                        "handleMoveEvent2");
-            }
             callOnDragStart();
         }
     }
@@ -535,10 +525,6 @@
      * Call this from a drag source view.
      */
     public boolean onControllerTouchEvent(MotionEvent ev) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "onControllerTouchEvent1");
-        }
         if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
             return false;
         }
@@ -559,10 +545,6 @@
                 break;
         }
 
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "onControllerTouchEvent2");
-        }
         return mDragDriver.onTouchEvent(ev);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 551f2d0..84fc94d 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -45,18 +45,10 @@
     public void onDragViewAnimationEnd() { }
 
     public boolean onTouchEvent(MotionEvent ev) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "onTouchEvent " + ev);
-        }
         final int action = ev.getAction();
 
         switch (action) {
             case MotionEvent.ACTION_MOVE:
-                if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                    android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                            "onTouchEvent MOVE");
-                }
                 mEventListener.onDriverDragMove(ev.getX(), ev.getY());
                 break;
             case MotionEvent.ACTION_UP:
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 9f902ed..8de2f57 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -17,6 +17,10 @@
 
 package com.android.launcher3.dragndrop;
 
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.getMode;
+import static android.view.View.MeasureSpec.getSize;
+
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
 import android.animation.Animator;
@@ -29,12 +33,14 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.AbstractFloatingView;
@@ -42,6 +48,7 @@
 import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.Interpolators;
@@ -52,6 +59,7 @@
 import com.android.launcher3.uioverrides.UiFactory;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.views.Transposable;
 
 import java.util.ArrayList;
 
@@ -126,10 +134,6 @@
     protected boolean findActiveController(MotionEvent ev) {
         if (mActivity.getStateManager().getState().disableInteraction) {
             // You Shall Not Pass!!!
-            if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                        "mActiveController = null");
-            }
             mActiveController = null;
             return true;
         }
@@ -264,10 +268,10 @@
         Rect r = new Rect();
         getViewRectRelativeToSelf(dragView, r);
 
-        int coord[] = new int[2];
+        float coord[] = new float[2];
         float childScale = child.getScaleX();
-        coord[0] = lp.x + (int) (child.getMeasuredWidth() * (1 - childScale) / 2);
-        coord[1] = lp.y + (int) (child.getMeasuredHeight() * (1 - childScale) / 2);
+        coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
+        coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
 
         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
         // the correct coordinates (above) and use these to determine the final location
@@ -275,8 +279,8 @@
         // We need to account for the scale of the child itself, as the above only accounts for
         // for the scale in parents.
         scale *= childScale;
-        int toX = coord[0];
-        int toY = coord[1];
+        int toX = Math.round(coord[0]);
+        int toY = Math.round(coord[1]);
         float toScale = scale;
         if (child instanceof TextView) {
             TextView tv = (TextView) child;
@@ -555,4 +559,159 @@
     public WorkspaceAndHotseatScrim getScrim() {
         return mScrim;
     }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        RotationMode rotation = mActivity.getRotationMode();
+        int count = getChildCount();
+
+        if (!rotation.isTransposed
+                || getMode(widthMeasureSpec) != EXACTLY
+                || getMode(heightMeasureSpec) != EXACTLY) {
+
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                child.setRotation(rotation.surfaceRotation);
+            }
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        } else {
+
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+                if (child.getVisibility() == GONE) {
+                    continue;
+                }
+                if (!(child instanceof Transposable)) {
+                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+                } else {
+                    measureChildWithMargins(child, heightMeasureSpec, 0, widthMeasureSpec, 0);
+
+                    child.setPivotX(child.getMeasuredWidth() / 2);
+                    child.setPivotY(child.getMeasuredHeight() / 2);
+                    child.setRotation(rotation.surfaceRotation);
+                }
+            }
+            setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec));
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        RotationMode rotation = mActivity.getRotationMode();
+        if (!rotation.isTransposed) {
+            super.onLayout(changed, left, top, right, bottom);
+            return;
+        }
+
+        final int count = getChildCount();
+
+        final int parentWidth = right - left;
+        final int parentHeight = bottom - top;
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == GONE) {
+                continue;
+            }
+
+            final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) child.getLayoutParams();
+
+            if (lp instanceof LayoutParams) {
+                final LayoutParams dlp = (LayoutParams) lp;
+                if (dlp.customPosition) {
+                    child.layout(dlp.x, dlp.y, dlp.x + dlp.width, dlp.y + dlp.height);
+                    continue;
+                }
+            }
+
+            final int width = child.getMeasuredWidth();
+            final int height = child.getMeasuredHeight();
+
+            int childLeft;
+            int childTop;
+
+            int gravity = lp.gravity;
+            if (gravity == -1) {
+                gravity = Gravity.TOP | Gravity.START;
+            }
+
+            final int layoutDirection = getLayoutDirection();
+
+            int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
+            int horizontalGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+            int verticalGravity = absoluteGravity & Gravity.VERTICAL_GRAVITY_MASK;
+
+            if (child instanceof Transposable) {
+                if (rotation == RotationMode.SEASCAPE) {
+                    if (horizontalGravity == Gravity.RIGHT) {
+                        horizontalGravity = Gravity.LEFT;
+                    } else if (horizontalGravity == Gravity.LEFT) {
+                        horizontalGravity = Gravity.RIGHT;
+                    }
+
+                    if (verticalGravity == Gravity.TOP) {
+                        verticalGravity = Gravity.BOTTOM;
+                    } else if (verticalGravity == Gravity.BOTTOM) {
+                        verticalGravity = Gravity.TOP;
+                    }
+                }
+
+                switch (horizontalGravity) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childTop = (parentHeight - height) / 2 +
+                                lp.topMargin - lp.bottomMargin;
+                        break;
+                    case Gravity.RIGHT:
+                        childTop = width / 2 + lp.rightMargin - height / 2;
+                        break;
+                    case Gravity.LEFT:
+                    default:
+                        childTop = parentHeight - lp.leftMargin - width / 2 - height / 2;
+                }
+
+                switch (verticalGravity) {
+                    case Gravity.CENTER_VERTICAL:
+                        childLeft = (parentWidth - width) / 2 +
+                                lp.leftMargin - lp.rightMargin;
+                        break;
+                    case Gravity.BOTTOM:
+                        childLeft = parentWidth - width / 2 - height / 2 - lp.bottomMargin;
+                        break;
+                    case Gravity.TOP:
+                    default:
+                        childLeft = height / 2 - width / 2 + lp.topMargin;
+                }
+            } else {
+                switch (horizontalGravity) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = (parentWidth - width) / 2 +
+                                lp.leftMargin - lp.rightMargin;
+                        break;
+                    case Gravity.RIGHT:
+                        childLeft = parentWidth - width - lp.rightMargin;
+                        break;
+                    case Gravity.LEFT:
+                    default:
+                        childLeft = lp.leftMargin;
+                }
+
+                switch (verticalGravity) {
+                    case Gravity.TOP:
+                        childTop = lp.topMargin;
+                        break;
+                    case Gravity.CENTER_VERTICAL:
+                        childTop = (parentHeight - height) / 2 +
+                                lp.topMargin - lp.bottomMargin;
+                        break;
+                    case Gravity.BOTTOM:
+                        childTop = parentHeight - height - lp.bottomMargin;
+                        break;
+                    default:
+                        childTop = lp.topMargin;
+                }
+            }
+
+            child.layout(childLeft, childTop, childLeft + width, childTop + height);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 4dbff1c..389e852 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -24,6 +24,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.annotation.SuppressLint;
+import android.appwidget.AppWidgetHostView;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -61,8 +62,10 @@
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.Workspace.ItemOperator;
+import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
@@ -121,6 +124,7 @@
     private static final int REORDER_DELAY = 250;
     private static final int ON_EXIT_CLOSE_DELAY = 400;
     private static final Rect sTempRect = new Rect();
+    private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10;
 
     private static String sDefaultFolderName;
     private static String sHintText;
@@ -430,21 +434,44 @@
         if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
             mCurrentAnimator.cancel();
         }
+        final Workspace workspace = mLauncher.getWorkspace();
+        final CellLayout currentCellLayout =
+                (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
+        final boolean useHardware = shouldUseHardwareLayerForAnimation(currentCellLayout);
+        final boolean wasHardwareAccelerated = currentCellLayout.isHardwareLayerEnabled();
+
         a.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
+                if (useHardware) {
+                    currentCellLayout.enableHardwareLayer(true);
+                }
                 mState = STATE_ANIMATING;
                 mCurrentAnimator = a;
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (useHardware) {
+                    currentCellLayout.enableHardwareLayer(wasHardwareAccelerated);
+                }
                 mCurrentAnimator = null;
             }
         });
         a.start();
     }
 
+    private boolean shouldUseHardwareLayerForAnimation(CellLayout currentCellLayout) {
+        int folderCount = 0;
+        final ShortcutAndWidgetContainer container = currentCellLayout.getShortcutsAndWidgets();
+        for (int i = container.getChildCount() - 1; i >= 0; --i) {
+            final View child = container.getChildAt(i);
+            if (child instanceof AppWidgetHostView) return false;
+            if (child instanceof FolderIcon) ++folderCount;
+        }
+        return folderCount >= MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION;
+    }
+
     /**
      * Opens the user folder described by the specified tag. The opening of the folder
      * is animated relative to the specified View. If the View is null, no animation
@@ -869,7 +896,7 @@
         DeviceProfile grid = mLauncher.getDeviceProfile();
 
         DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
-        DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer);
+        DragLayer parent = mLauncher.getDragLayer();
         int width = getFolderWidth();
         int height = getFolderHeight();
 
@@ -881,8 +908,7 @@
 
         // We need to bound the folder to the currently visible workspace area
         if (mLauncher.getStateManager().getState().overviewUi) {
-            mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(),
-                    sTempRect);
+            parent.getDescendantRectRelativeToSelf(mLauncher.getOverviewPanel(), sTempRect);
         } else {
             mLauncher.getWorkspace().getPageAreaRelativeToDragLayer(sTempRect);
         }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 02242a3..6fa9ba9 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -159,7 +159,7 @@
                     "is dependent on this");
         }
 
-        DeviceProfile grid = launcher.getDeviceProfile();
+        DeviceProfile grid = launcher.getWallpaperDeviceProfile();
         FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
                 .inflate(resId, group, false);
 
@@ -174,7 +174,7 @@
         icon.setOnClickListener(ItemClickHandler.INSTANCE);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
-        icon.mDotRenderer = launcher.getDeviceProfile().mDotRenderer;
+        icon.mDotRenderer = grid.mDotRenderer;
         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
@@ -508,7 +508,8 @@
     public void drawDot(Canvas canvas) {
         if ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0) {
             Rect iconBounds = mDotParams.iconBounds;
-            BubbleTextView.getIconBounds(this, iconBounds, mLauncher.getDeviceProfile().iconSizePx);
+            BubbleTextView.getIconBounds(this, iconBounds,
+                    mLauncher.getWallpaperDeviceProfile().iconSizePx);
 
             // If we are animating to the accepting state, animate the dot out.
             mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index ac908f4..46df77a 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
 /**
@@ -135,7 +134,7 @@
         mBgColor = ta.getColor(R.styleable.FolderIconPreview_android_colorPrimary, 0);
         ta.recycle();
 
-        DeviceProfile grid = activity.getDeviceProfile();
+        DeviceProfile grid = activity.getWallpaperDeviceProfile();
         previewSize = grid.folderIconSizePx;
 
         basePreviewOffsetX = (availableSpaceX - previewSize) / 2;
diff --git a/src/com/android/launcher3/graphics/RotationMode.java b/src/com/android/launcher3/graphics/RotationMode.java
new file mode 100644
index 0000000..1b2cbdb
--- /dev/null
+++ b/src/com/android/launcher3/graphics/RotationMode.java
@@ -0,0 +1,59 @@
+/*
+ * 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.launcher3.graphics;
+
+import android.graphics.Rect;
+
+public abstract class RotationMode {
+
+    public final float surfaceRotation;
+    public final boolean isTransposed;
+
+    private RotationMode(float surfaceRotation) {
+        this.surfaceRotation = surfaceRotation;
+        isTransposed = surfaceRotation != 0;
+    }
+
+    public final void mapRect(Rect rect, Rect out) {
+        mapRect(rect.left, rect.top, rect.right, rect.bottom, out);
+    }
+
+    public void mapRect(int left, int top, int right, int bottom, Rect out) {
+        out.set(left, top, right, bottom);
+    }
+
+    public static RotationMode NORMAL = new RotationMode(0) { };
+
+    public static RotationMode LANDSCAPE = new RotationMode(-90) {
+        @Override
+        public void mapRect(int left, int top, int right, int bottom, Rect out) {
+            out.left = top;
+            out.top = right;
+            out.right = bottom;
+            out.bottom = left;
+        }
+    };
+
+    public static RotationMode SEASCAPE = new RotationMode(90) {
+        @Override
+        public void mapRect(int left, int top, int right, int bottom, Rect out) {
+            out.left = bottom;
+            out.top = left;
+            out.right = top;
+            out.bottom = right;
+        }
+    };
+}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 593dbd4..047f486 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
@@ -173,8 +172,7 @@
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             BaseDragLayer dl = getPopupContainer();
-            final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
-            if (!cameFromNavBar && !dl.isEventOverView(this, ev)) {
+            if (!dl.isEventOverView(this, ev)) {
                 mLauncher.getUserEventDispatcher().logActionTapOutside(
                         LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
                 close(true);
@@ -192,10 +190,6 @@
      * @return the container if shown or null.
      */
     public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "PopupContainerWithArrow.showForIcon");
-        }
         Launcher launcher = Launcher.getLauncher(icon.getContext());
         if (getOpen(launcher) != null) {
             // There is already an items container open, so don't open this one.
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index a1871ff..35fc873 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -233,11 +233,6 @@
 
     @Override
     public void onDragStart(boolean start) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "AbstractStateChangeTouchController.onDragStart() called with: start = [" +
-                            start + "]");
-        }
         mStartState = mLauncher.getStateManager().getState();
         if (mStartState == ALL_APPS) {
             mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS;
@@ -269,11 +264,6 @@
     public boolean onDrag(float displacement) {
         float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift);
         float progress = deltaProgress + mStartProgress;
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "AbstractStateChangeTouchController.onDrag() called with: displacement = [" +
-                            displacement + "], progress = [" + progress + "]");
-        }
         updateProgress(progress);
         boolean isDragTowardPositive = mSwipeDirection.isPositive(
                 displacement - mDisplacementShift);
@@ -393,12 +383,6 @@
                     ? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS;
             targetState = (interpolatedProgress > successProgress) ? mToState : mFromState;
         }
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "AbstractStateChangeTouchController.onDragEnd() called with: velocity = [" +
-                            velocity + "], fling = [" + fling + "], target state: " +
-                            targetState.getClass().getSimpleName());
-        }
 
         final float endProgress;
         final float startProgress;
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 026770c..0650001 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -66,14 +66,6 @@
     }
 
     private static void onClick(View v, String sourceContainer) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "onClick() called with: v = [" + v.getClass().getSimpleName() +
-                            "], sourceContainer = [" +
-                            (sourceContainer != null ?
-                                    sourceContainer.getClass().getSimpleName() : "null")
-                            + "]");
-        }
         // Make sure that rogue clicks don't get through while allapps is launching, or after the
         // view has detached (it's possible for this to happen if the view is removed mid touch).
         if (v.getWindowToken() == null) {
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 003b442..babbcdd 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -74,10 +74,6 @@
     }
 
     private static boolean onAllAppsItemLongClick(View v) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "onAllAppsItemLongClick");
-        }
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         // When we have exited all apps or are in transition, disregard long clicks
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index a45f17d..0c44012 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.util;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.ColorMatrix;
@@ -26,12 +27,34 @@
 import android.util.TypedValue;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.uioverrides.WallpaperColorInfo;
 
 /**
  * Various utility methods associated with theming.
  */
 public class Themes {
 
+    public static int getActivityThemeRes(Context context) {
+        WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(context);
+        boolean darkTheme;
+        if (Utilities.ATLEAST_Q) {
+            Configuration configuration = context.getResources().getConfiguration();
+            int nightMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+            darkTheme = nightMode == Configuration.UI_MODE_NIGHT_YES;
+        } else {
+            darkTheme = wallpaperColorInfo.isDark();
+        }
+
+        if (darkTheme) {
+            return wallpaperColorInfo.supportsDarkText() ?
+                    R.style.AppTheme_Dark_DarkText : R.style.AppTheme_Dark;
+        } else {
+            return wallpaperColorInfo.supportsDarkText() ?
+                    R.style.AppTheme_DarkText : R.style.AppTheme;
+        }
+    }
+
     public static String getDefaultBodyFont(Context context) {
         TypedArray ta = context.obtainStyledAttributes(android.R.style.TextAppearance_DeviceDefault,
                 new int[]{android.R.attr.fontFamily});
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index c9cdeff..0331a86 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -22,6 +22,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.dot.DotInfo;
 
 /**
@@ -56,6 +57,19 @@
 
     DeviceProfile getDeviceProfile();
 
+    /**
+     * Device profile to be used by UI elements which are shown directly on top of the wallpaper
+     * and whose presentation is tied to the wallpaper (and physical device) and not the activity
+     * configuration.
+     */
+    default DeviceProfile getWallpaperDeviceProfile() {
+        return getDeviceProfile();
+    }
+
+    default RotationMode getRotationMode() {
+        return RotationMode.NORMAL;
+    }
+
     static ActivityContext lookupContext(Context context) {
         if (context instanceof ActivityContext) {
             return (ActivityContext) context;
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 66cd536..3c81bcf 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -22,13 +22,19 @@
 
 import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
 
+import android.annotation.TargetApi;
 import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.util.Property;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewDebug;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 
@@ -74,18 +80,33 @@
                 }
             };
 
-    protected final int[] mTmpXY = new int[2];
+    // Touch is being dispatched through the normal view dispatch system
+    private static final int TOUCH_DISPATCHING_VIEW = 1 << 0;
+    // Touch is being dispatched through the normal view dispatch system, and started at the
+    // system gesture region
+    private static final int TOUCH_DISPATCHING_GESTURE = 1 << 1;
+    // Touch is being dispatched through a proxy from InputMonitor
+    private static final int TOUCH_DISPATCHING_PROXY = 1 << 2;
+
+    protected final float[] mTmpXY = new float[2];
+    protected final float[] mTmpRectPoints = new float[4];
     protected final Rect mHitRect = new Rect();
 
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private final RectF mSystemGestureRegion = new RectF();
+    private int mTouchDispatchState = 0;
+
     protected final T mActivity;
     private final MultiValueAlpha mMultiValueAlpha;
 
+    // All the touch controllers for the view
     protected TouchController[] mControllers;
+    // Touch controller which is currently active for the normal view dispatch
     protected TouchController mActiveController;
-    private TouchCompleteListener mTouchCompleteListener;
+    // Touch controller which is being used for the proxy events
+    protected TouchController mProxyTouchController;
 
-    // Object controlling the current touch interaction
-    private Object mCurrentTouchOwner;
+    private TouchCompleteListener mTouchCompleteListener;
 
     public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
         super(context, attrs);
@@ -113,33 +134,29 @@
         return findActiveController(ev);
     }
 
-    protected boolean findActiveController(MotionEvent ev) {
-        if (com.android.launcher3.TestProtocol.sDebugTracing) {
-            android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                    "mActiveController = null");
-        }
-        mActiveController = null;
-
+    private TouchController findControllerToHandleTouch(MotionEvent ev) {
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
         if (topView != null && topView.onControllerInterceptTouchEvent(ev)) {
-            if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                        "setting controller1: " + topView.getClass().getSimpleName());
-            }
-            mActiveController = topView;
-            return true;
+            return topView;
         }
 
         for (TouchController controller : mControllers) {
             if (controller.onControllerInterceptTouchEvent(ev)) {
-                if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                    android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                            "setting controller1: " + controller.getClass().getSimpleName());
-                }
-                mActiveController = controller;
-                return true;
+                return controller;
             }
         }
+        return null;
+    }
+
+    protected boolean findActiveController(MotionEvent ev) {
+        mActiveController = null;
+        if ((mTouchDispatchState & (TOUCH_DISPATCHING_GESTURE | TOUCH_DISPATCHING_PROXY)) == 0) {
+            // Only look for controllers if we are not dispatching from gesture area and proxy is
+            // not active
+            mActiveController = findControllerToHandleTouch(ev);
+
+            if (mActiveController != null) return true;
+        }
         return false;
     }
 
@@ -205,17 +222,8 @@
         }
 
         if (mActiveController != null) {
-            if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                        "BaseDragLayer before onControllerTouchEvent " +
-                                mActiveController.getClass().getSimpleName());
-            }
             return mActiveController.onControllerTouchEvent(ev);
         } else {
-            if (com.android.launcher3.TestProtocol.sDebugTracing) {
-                android.util.Log.d(com.android.launcher3.TestProtocol.NO_DRAG_TAG,
-                        "BaseDragLayer no controller");
-            }
             // In case no child view handled the touch event, we may not get onIntercept anymore
             return findActiveController(ev);
         }
@@ -223,40 +231,75 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        return dispatchTouchEvent(this, ev);
-    }
+        switch (ev.getAction()) {
+            case ACTION_DOWN: {
+                float x = ev.getX();
+                float y = ev.getY();
+                mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
 
-    public boolean dispatchTouchEvent(Object caller, MotionEvent ev) {
-        return verifyTouchDispatch(caller, ev) && super.dispatchTouchEvent(ev);
+                if ((y < mSystemGestureRegion.top
+                        || x < mSystemGestureRegion.left
+                        || x > (getWidth() - mSystemGestureRegion.right)
+                        || y > (getHeight() - mSystemGestureRegion.bottom))) {
+                    mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE;
+                } else {
+                    mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+                }
+                break;
+            }
+            case ACTION_CANCEL:
+            case ACTION_UP:
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE;
+                mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW;
+                break;
+        }
+        super.dispatchTouchEvent(ev);
+
+        // We want to get all events so that mTouchDispatchSource is maintained properly
+        return true;
     }
 
     /**
-     * Returns true if the {@param caller} is allowed to dispatch {@param ev} on this view,
-     * false otherwise.
+     * Called before we are about to receive proxy events.
+     *
+     * @return false if we can't handle proxy at this time
      */
-    private boolean verifyTouchDispatch(Object caller, MotionEvent ev) {
-        int action = ev.getAction();
-        if (action == ACTION_DOWN) {
-            if (mCurrentTouchOwner != null) {
-                // Another touch in progress.
-                ev.setAction(ACTION_CANCEL);
-                super.dispatchTouchEvent(ev);
-                ev.setAction(action);
-            }
-            mCurrentTouchOwner = caller;
-            return true;
-        }
-        if (mCurrentTouchOwner != caller) {
-            // Someone else is controlling the touch
+    public boolean prepareProxyEventStarting() {
+        mProxyTouchController = null;
+        if ((mTouchDispatchState & TOUCH_DISPATCHING_VIEW) != 0 && mActiveController != null) {
+            // We are already dispatching using view system and have an active controller, we can't
+            // handle another controller.
+
+            // This flag was already cleared in proxy ACTION_UP or ACTION_CANCEL. Added here just
+            // to be safe
+            mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
             return false;
         }
-        if (action == ACTION_UP || action == ACTION_CANCEL) {
-            mCurrentTouchOwner = null;
-        }
+
+        mTouchDispatchState |= TOUCH_DISPATCHING_PROXY;
         return true;
     }
 
     /**
+     * Proxies the touch events to the gesture handlers
+     */
+    public boolean proxyTouchEvent(MotionEvent ev) {
+        boolean handled;
+        if (mProxyTouchController != null) {
+            handled = mProxyTouchController.onControllerTouchEvent(ev);
+        } else {
+            mProxyTouchController = findControllerToHandleTouch(ev);
+            handled = mProxyTouchController != null;
+        }
+        int action = ev.getAction();
+        if (action == ACTION_UP || action == ACTION_CANCEL) {
+            mProxyTouchController = null;
+            mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY;
+        }
+        return handled;
+    }
+
+    /**
      * Determine the rect of the descendant in this DragLayer's coordinates
      *
      * @param descendant The descendant whose coordinates we want to find.
@@ -264,14 +307,16 @@
      * @return The factor by which this descendant is scaled relative to this DragLayer.
      */
     public float getDescendantRectRelativeToSelf(View descendant, Rect r) {
-        mTmpXY[0] = 0;
-        mTmpXY[1] = 0;
-        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
-
-        r.set(mTmpXY[0], mTmpXY[1],
-                (int) (mTmpXY[0] + scale * descendant.getMeasuredWidth()),
-                (int) (mTmpXY[1] + scale * descendant.getMeasuredHeight()));
-        return scale;
+        mTmpRectPoints[0] = 0;
+        mTmpRectPoints[1] = 0;
+        mTmpRectPoints[2] = descendant.getWidth();
+        mTmpRectPoints[3] = descendant.getHeight();
+        float s = getDescendantCoordRelativeToSelf(descendant, mTmpRectPoints);
+        r.left = Math.round(Math.min(mTmpRectPoints[0], mTmpRectPoints[2]));
+        r.top = Math.round(Math.min(mTmpRectPoints[1], mTmpRectPoints[3]));
+        r.right = Math.round(Math.max(mTmpRectPoints[0], mTmpRectPoints[2]));
+        r.bottom = Math.round(Math.max(mTmpRectPoints[1], mTmpRectPoints[3]));
+        return s;
     }
 
     public float getLocationInDragLayer(View child, int[] loc) {
@@ -281,6 +326,14 @@
     }
 
     public float getDescendantCoordRelativeToSelf(View descendant, int[] coord) {
+        mTmpXY[0] = coord[0];
+        mTmpXY[1] = coord[1];
+        float scale = getDescendantCoordRelativeToSelf(descendant, mTmpXY);
+        Utilities.roundArray(mTmpXY, coord);
+        return scale;
+    }
+
+    public float getDescendantCoordRelativeToSelf(View descendant, float[] coord) {
         return getDescendantCoordRelativeToSelf(descendant, coord, false);
     }
 
@@ -296,17 +349,27 @@
      *         this scale factor is assumed to be equal in X and Y, and so if at any point this
      *         assumption fails, we will need to return a pair of scale factors.
      */
-    public float getDescendantCoordRelativeToSelf(View descendant, int[] coord,
+    public float getDescendantCoordRelativeToSelf(View descendant, float[] coord,
             boolean includeRootScroll) {
         return Utilities.getDescendantCoordRelativeToAncestor(descendant, this,
                 coord, includeRootScroll);
     }
 
     /**
+     * Inverse of {@link #getDescendantCoordRelativeToSelf(View, float[])}.
+     */
+    public void mapCoordInSelfToDescendant(View descendant, float[] coord) {
+        Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
+    }
+
+    /**
      * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
      */
     public void mapCoordInSelfToDescendant(View descendant, int[] coord) {
-        Utilities.mapCoordInSelfToDescendant(descendant, this, coord);
+        mTmpXY[0] = coord[0];
+        mTmpXY[1] = coord[1];
+        Utilities.mapCoordInSelfToDescendant(descendant, this, mTmpXY);
+        Utilities.roundArray(mTmpXY, coord);
     }
 
     public void getViewRectRelativeToSelf(View v, Rect r) {
@@ -423,4 +486,13 @@
             }
         }
     }
+
+    @TargetApi(Build.VERSION_CODES.Q)
+    public void updateTouchExcludeRegion(WindowInsets insets) {
+        if (Utilities.ATLEAST_Q) {
+            Insets gestureInsets = insets.getMandatorySystemGestureInsets();
+            mSystemGestureRegion.set(gestureInsets.left, gestureInsets.top,
+                    gestureInsets.right, gestureInsets.bottom);
+        }
+    }
 }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index f2fc718..24a8be5 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 
@@ -23,6 +26,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -61,23 +65,23 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
-import static com.android.launcher3.Utilities.mapToRange;
-
 /**
  * A view that is created to look like another view with the purpose of creating fluid animations.
  */
-
+@TargetApi(Build.VERSION_CODES.Q)
 public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
 
     public static final float SHAPE_PROGRESS_DURATION = 0.15f;
     private static final int FADE_DURATION_MS = 200;
     private static final Rect sTmpRect = new Rect();
+    private static final Object[] sTmpObjArray = new Object[1];
 
     private Runnable mEndRunnable;
     private CancellationSignal mLoadIconSignal;
 
     private final int mBlurSizeOutline;
 
+    private boolean mIsVerticalBarLayout = false;
     private boolean mIsAdaptiveIcon = false;
 
     private @Nullable Drawable mForeground;
@@ -185,14 +189,16 @@
      * @param v The view to copy
      * @param positionOut Rect that will hold the size and position of v.
      */
-    private void matchPositionOf(Launcher launcher, View v, Rect positionOut) {
-        Utilities.getLocationBoundsForView(launcher, v, positionOut);
-        final LayoutParams lp = new LayoutParams(positionOut.width(), positionOut.height());
+    private void matchPositionOf(Launcher launcher, View v, RectF positionOut) {
+        getLocationBoundsForView(launcher, v, positionOut);
+        final LayoutParams lp = new LayoutParams(
+                Math.round(positionOut.width()),
+                Math.round(positionOut.height()));
         lp.ignoreInsets = true;
 
         // Position the floating view exactly on top of the original
-        lp.leftMargin = positionOut.left;
-        lp.topMargin = positionOut.top;
+        lp.leftMargin = Math.round(positionOut.left);
+        lp.topMargin = Math.round(positionOut.top);
         setLayoutParams(lp);
         // Set the properties here already to make sure they are available when running the first
         // animation frame.
@@ -200,6 +206,57 @@
                 + lp.height);
     }
 
+    /**
+     * Returns the location bounds of a view.
+     * - For DeepShortcutView, we return the bounds of the icon view.
+     * - For BubbleTextView, we return the icon bounds.
+     */
+    private void getLocationBoundsForView(Launcher launcher, View v, RectF outRect) {
+        final boolean isBubbleTextView = v instanceof BubbleTextView;
+        final boolean isFolderIcon = v instanceof FolderIcon;
+
+        // Deep shortcut views have their icon drawn in a separate view.
+        final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView;
+
+        final View targetView;
+        boolean ignoreTransform = false;
+
+        if (v instanceof DeepShortcutView) {
+            targetView = ((DeepShortcutView) v).getIconView();
+        } else if (fromDeepShortcutView) {
+            DeepShortcutView view = (DeepShortcutView) v.getParent();
+            targetView = view.getIconView();
+        } else if ((isBubbleTextView || isFolderIcon) && v.getTag() instanceof ItemInfo
+                && (((ItemInfo) v.getTag()).container == CONTAINER_DESKTOP
+                || ((ItemInfo) v.getTag()).container == CONTAINER_HOTSEAT)) {
+            targetView = v;
+            ignoreTransform = true;
+        } else {
+            targetView = v;
+        }
+
+        float[] points = new float[] {0, 0, targetView.getWidth(), targetView.getHeight()};
+        Utilities.getDescendantCoordRelativeToAncestor(targetView, launcher.getDragLayer(), points,
+                false, ignoreTransform);
+
+        float viewLocationLeft = Math.min(points[0], points[2]);
+        float viewLocationTop = Math.min(points[1], points[3]);
+
+        final Rect iconRect = new Rect();
+        if (isBubbleTextView && !fromDeepShortcutView) {
+            ((BubbleTextView) v).getIconBounds(iconRect);
+        } else if (isFolderIcon) {
+            ((FolderIcon) v).getPreviewBounds(iconRect);
+        } else {
+            iconRect.set(0, 0, Math.abs(Math.round(points[2] - points[0])),
+                    Math.abs(Math.round(points[3] - points[1])));
+        }
+        viewLocationLeft += iconRect.left;
+        viewLocationTop += iconRect.top;
+        outRect.set(viewLocationLeft, viewLocationTop, viewLocationLeft + iconRect.width(),
+                viewLocationTop + iconRect.height());
+    }
+
     @WorkerThread
     private void getIcon(Launcher launcher, View v, ItemInfo info, boolean isOpening,
             Runnable onIconLoadedRunnable, CancellationSignal loadIconSignal) {
@@ -207,10 +264,7 @@
         Drawable drawable = null;
         boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
                 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
-        if (!supportsAdaptiveIcons && v instanceof BubbleTextView) {
-            // Similar to DragView, we simply use the BubbleTextView icon here.
-            drawable = ((BubbleTextView) v).getIcon();
-        }
+        Drawable btvIcon = v instanceof BubbleTextView ? ((BubbleTextView) v).getIcon() : null;
         if (info instanceof SystemShortcut) {
             if (v instanceof ImageView) {
                 drawable = ((ImageView) v).getDrawable();
@@ -219,10 +273,24 @@
             } else {
                 drawable = v.getBackground();
             }
-        }
-        if (drawable == null) {
-            drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
-                    false, new Object[1]);
+        } else {
+            if (supportsAdaptiveIcons) {
+                drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
+                        false, sTmpObjArray);
+                if (!(drawable instanceof AdaptiveIconDrawable)) {
+                    // The drawable we get back is not an adaptive icon, so we need to use the
+                    // BubbleTextView icon that is already legacy treated.
+                    drawable = btvIcon;
+                }
+            } else {
+                if (v instanceof BubbleTextView) {
+                    // Similar to DragView, we simply use the BubbleTextView icon here.
+                    drawable = btvIcon;
+                } else {
+                    drawable = Utilities.getFullDrawable(launcher, info, lp.width, lp.height,
+                            false, sTmpObjArray);
+                }
+            }
         }
 
         Drawable finalDrawable = drawable == null ? null
@@ -273,7 +341,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 +386,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);
     }
 
@@ -405,11 +478,12 @@
      * @param isOpening True if this view replaces the icon for app open animation.
      */
     public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView,
-            boolean hideOriginal, Rect positionOut, boolean isOpening, FloatingIconView recycle) {
+            boolean hideOriginal, RectF positionOut, boolean isOpening, FloatingIconView recycle) {
         if (recycle != null) {
             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/src/com/android/launcher3/views/Transposable.java b/src/com/android/launcher3/views/Transposable.java
new file mode 100644
index 0000000..929c1aa
--- /dev/null
+++ b/src/com/android/launcher3/views/Transposable.java
@@ -0,0 +1,26 @@
+/**
+ * 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.launcher3.views;
+
+import com.android.launcher3.graphics.RotationMode;
+
+/**
+ * Indicates that a view can be transposed.
+ */
+public interface Transposable {
+
+    RotationMode getRotationMode();
+}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 3686493..75ff66e 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -99,7 +99,8 @@
         </activity>
         <activity
             android:name="com.android.launcher3.testcomponent.BaseTestingActivity"
-            android:label="LauncherTestApp">
+            android:label="LauncherTestApp"
+            android:taskAffinity="com.android.launcher3.testcomponent.Affinity1">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -111,5 +112,77 @@
             <meta-data android:name="android.app.shortcuts"
                        android:resource="@xml/shortcuts"/>
         </activity>
+        <activity-alias android:name="Activity2"
+                        android:label="TestActivity2"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity3"
+                        android:label="TestActivity3"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity4"
+                        android:label="TestActivity4"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity5"
+                        android:label="TestActivity5"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity6"
+                        android:label="TestActivity6"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity7"
+                        android:label="TestActivity7"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity8"
+                        android:label="TestActivity8"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity9"
+                        android:label="TestActivity9"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity10"
+                        android:label="TestActivity10"
+                        android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity-alias>
     </application>
 </manifest>
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity1.java b/tests/src/com/android/launcher3/testcomponent/MainActivity1.java
deleted file mode 100644
index 7ef0ab6..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity1.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity1 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity10.java b/tests/src/com/android/launcher3/testcomponent/MainActivity10.java
deleted file mode 100644
index 11df0d2..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity10.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity10 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity2.java b/tests/src/com/android/launcher3/testcomponent/MainActivity2.java
deleted file mode 100644
index dfbba3e..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity2.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity2 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity3.java b/tests/src/com/android/launcher3/testcomponent/MainActivity3.java
deleted file mode 100644
index 87d77d9..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity3.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity3 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity4.java b/tests/src/com/android/launcher3/testcomponent/MainActivity4.java
deleted file mode 100644
index dabd2c5..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity4.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity4 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity5.java b/tests/src/com/android/launcher3/testcomponent/MainActivity5.java
deleted file mode 100644
index e58210c..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity5.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity5 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity6.java b/tests/src/com/android/launcher3/testcomponent/MainActivity6.java
deleted file mode 100644
index 005d248..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity6.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity6 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity7.java b/tests/src/com/android/launcher3/testcomponent/MainActivity7.java
deleted file mode 100644
index 0f21549..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity7.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity7 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity8.java b/tests/src/com/android/launcher3/testcomponent/MainActivity8.java
deleted file mode 100644
index 5ba5c55..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity8.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity8 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/testcomponent/MainActivity9.java b/tests/src/com/android/launcher3/testcomponent/MainActivity9.java
deleted file mode 100644
index 82b1fcd..0000000
--- a/tests/src/com/android/launcher3/testcomponent/MainActivity9.java
+++ /dev/null
@@ -1,24 +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.launcher3.testcomponent;
-
-import android.app.Activity;
-
-/**
- * Base activity with utility methods to help automate testing.
- */
-public class MainActivity9 extends Activity {
-}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index a37218b..43bdb9f 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -17,6 +17,11 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_2BUTTON_OVERLAY;
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_3BUTTON_OVERLAY;
+import static com.android.systemui.shared.system.QuickStepContract.NAV_BAR_MODE_GESTURAL_OVERLAY;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -24,11 +29,13 @@
 
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
@@ -57,6 +64,7 @@
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.rules.TestRule;
@@ -67,6 +75,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.lang.reflect.Method;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -102,6 +111,68 @@
         }
         if (TestHelpers.isInLauncherProcess()) Utilities.enableRunningInTestHarnessForTests();
         mLauncher = new LauncherInstrumentation(instrumentation);
+
+        // b/130558787; b/131419978
+        if (TestHelpers.isInLauncherProcess() && !LauncherInstrumentation.needSlowGestures()) {
+            try {
+                Class systemProps = Class.forName("android.os.SystemProperties");
+                Method getInt = systemProps.getMethod("getInt", String.class, int.class);
+                int apiLevel = (int) getInt.invoke(null, "ro.product.first_api_level", 0);
+
+                if (apiLevel >= Build.VERSION_CODES.P) {
+                    setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.TWO_BUTTON);
+                }
+                if (apiLevel >= Build.VERSION_CODES.O && apiLevel < Build.VERSION_CODES.P) {
+                    setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.ZERO_BUTTON);
+                }
+                if (apiLevel < Build.VERSION_CODES.O) {
+                    setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
+                            LauncherInstrumentation.NavigationModel.THREE_BUTTON);
+                }
+            } catch (Throwable e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public void setActiveOverlay(String overlayPackage,
+            LauncherInstrumentation.NavigationModel expectedMode) {
+        setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
+                overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
+        setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
+                overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
+        setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
+                overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+
+        for (int i = 0; i != 100; ++i) {
+            if (mLauncher.getNavigationModel() == expectedMode) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+                return;
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        Assert.fail("Couldn't switch to " + overlayPackage);
+    }
+
+    private void setOverlayPackageEnabled(String overlayPackage, boolean enable) {
+        Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
+        final String action = enable ? "enable" : "disable";
+        try {
+            UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+                    "cmd overlay " + action + " " + overlayPackage);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 
     @Rule
@@ -357,6 +428,22 @@
                 mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), LONG_WAIT_TIME_MS));
     }
 
+    protected void startTestActivity(int activityNumber) {
+        final String packageName = getAppPackageName();
+        final Instrumentation instrumentation = getInstrumentation();
+        final Intent intent = instrumentation.getContext().getPackageManager().
+                getLaunchIntentForPackage(packageName);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setComponent(new ComponentName(packageName,
+                "com.android.launcher3.tests.Activity" + activityNumber));
+        instrumentation.getTargetContext().startActivity(intent);
+        assertTrue(packageName + " didn't start",
+                mDevice.wait(
+                        Until.hasObject(By.pkg(packageName).text("TestActivity" + activityNumber)),
+                        LONG_WAIT_TIME_MS));
+    }
+
     protected static String resolveSystemApp(String category) {
         return getInstrumentation().getContext().getPackageManager().resolveActivity(
                 new Intent(Intent.ACTION_MAIN).addCategory(category),
diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
index 357e029..48335a6 100644
--- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
+++ b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java
@@ -71,7 +71,7 @@
     }
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
+    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
         writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP));
 
@@ -86,7 +86,7 @@
     }
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
+    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_widget() throws Exception {
         // A non-restored widget with no config screen gets restored automatically.
         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
@@ -106,7 +106,7 @@
     }
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
+    // Convert test to TAPL; b/131116002
     public void testCustomProfileLoaded_with_folder() throws Exception {
         writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy)
                 .addApp(SETTINGS_APP, SETTINGS_APP)
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index c55bc72..581e886 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -33,7 +33,6 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.TestProtocol;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.tapl.AllApps;
 import com.android.launcher3.tapl.AppIcon;
@@ -239,12 +238,6 @@
         // Test starting a workspace app.
         final AppIcon app = workspace.tryGetWorkspaceAppIcon("Chrome");
         assertNotNull("No Chrome app in workspace", app);
-        assertNotNull("AppIcon.launch returned null",
-                app.launch(resolveSystemApp(Intent.CATEGORY_APP_BROWSER)));
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInBackground(launcher)));
     }
 
     public static void runIconLaunchFromAllAppsTest(AbstractLauncherUiTest test, AllApps allApps) {
@@ -329,20 +322,19 @@
     @Test
     @PortraitLandscape
     public void testDragAppIcon() throws Throwable {
-        try {
-            TestProtocol.sDebugTracing = true;
-            // 1. Open all apps and wait for load complete.
-            // 2. Drag icon to homescreen.
-            // 3. Verify that the icon works on homescreen.
-            mLauncher.getWorkspace().
-                    switchToAllApps().
-                    getAppIcon(APP_NAME).
-                    dragToWorkspace().
-                    getWorkspaceAppIcon(APP_NAME).
-                    launch(getAppPackageName());
-        } finally {
-            TestProtocol.sDebugTracing = false;
-        }
+        // 1. Open all apps and wait for load complete.
+        // 2. Drag icon to homescreen.
+        // 3. Verify that the icon works on homescreen.
+        mLauncher.getWorkspace().
+                switchToAllApps().
+                getAppIcon(APP_NAME).
+                dragToWorkspace().
+                getWorkspaceAppIcon(APP_NAME).
+                launch(getAppPackageName());
+        executeOnLauncher(launcher -> assertTrue(
+                "Launcher activity is the top activity; expecting another activity to be the top "
+                        + "one",
+                isInBackground(launcher)));
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 84452b4..06a8bca 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -71,25 +71,25 @@
     }
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
+    // Convert test to TAPL b/131116002
     public void testWidgetConfig() throws Throwable {
         runTest(false, true);
     }
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
+    @Ignore // b/121280703
     public void testWidgetConfig_rotate() throws Throwable {
         runTest(true, true);
     }
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
+    // Convert test to TAPL b/131116002
     public void testConfigCancelled() throws Throwable {
         runTest(false, false);
     }
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
+    @Ignore // b/121280703
     public void testConfigCancelled_rotate() throws Throwable {
         runTest(true, false);
     }
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index 90c339f..e802acb 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -49,19 +49,19 @@
     @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
     public void testDragIcon_portrait() throws Throwable {
         lockRotation(true);
         performTest();
     }
 
     @Test
-    @Ignore // Convert test to TAPL and enable them; b/131116002
+    @Ignore // b/121280703
     public void testDragIcon_landscape() throws Throwable {
         lockRotation(false);
         performTest();
     }
 
+    // Convert to TAPL b/131116002
     private void performTest() throws Throwable {
         clearHomescreen();
         mActivityMonitor.startLauncher();
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 4685c7d..19c5047 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -69,6 +69,8 @@
             if (mLauncher.getNavigationModel() != ZERO_BUTTON) {
                 final UiObject2 navBar = mLauncher.waitForSystemUiObject("navigation_bar_frame");
                 allAppsContainer.setGestureMargins(0, 0, 0, navBar.getVisibleBounds().height() + 1);
+            } else {
+                allAppsContainer.setGestureMargins(0, 0, 0, 100);
             }
             final BySelector appIconSelector = AppIcon.getAppIconSelector(appName, mLauncher);
             if (!hasClickableIcon(allAppsContainer, appIconSelector)) {
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..9a47aef 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;
 
@@ -142,7 +143,6 @@
     static void dragIconToWorkspace(
             LauncherInstrumentation launcher, Launchable launchable, Point dest,
             String longPressIndicator) {
-        launcher.getTestInfo(TestProtocol.REQUEST_ENABLE_DRAG_LOGGING);
         LauncherInstrumentation.log("dragIconToWorkspace: begin");
         final Point launchableCenter = launchable.getObject().getVisibleCenter();
         final long downTime = SystemClock.uptimeMillis();
@@ -156,7 +156,6 @@
                 downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest);
         LauncherInstrumentation.log("dragIconToWorkspace: end");
         launcher.waitUntilGone("drop_target_bar");
-        launcher.getTestInfo(TestProtocol.REQUEST_DISABLE_DRAG_LOGGING);
     }
 
     /**