Merge "Disable work profile tests" into sc-dev
diff --git a/Android.bp b/Android.bp
index 92cc36b..2c9d664 100644
--- a/Android.bp
+++ b/Android.bp
@@ -44,6 +44,7 @@
         "src/com/android/launcher3/ResourceUtils.java",
         "src/com/android/launcher3/testing/TestProtocol.java",
     ],
+    resource_dirs: [ ],
     manifest: "tests/tapl/AndroidManifest.xml",
     platform_apis: true,
 }
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 4fd2e40..4e72260 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -116,8 +116,7 @@
             android:theme="@style/AppItemActivityTheme"
             android:excludeFromRecents="true"
             android:autoRemoveFromRecents="true"
-            android:exported="true"
-            android:label="@string/action_add_to_workspace" >
+            android:exported="true">
             <intent-filter>
                 <action android:name="android.content.pm.action.CONFIRM_PIN_SHORTCUT" />
                 <action android:name="android.content.pm.action.CONFIRM_PIN_APPWIDGET" />
diff --git a/go/OWNERS b/go/OWNERS
new file mode 100644
index 0000000..903b3c4
--- /dev/null
+++ b/go/OWNERS
@@ -0,0 +1,2 @@
+rajekumar@google.com
+spivack@google.com
diff --git a/go/quickstep/res/values/strings.xml b/go/quickstep/res/values/strings.xml
index 71e2f3a..61c8cd9 100644
--- a/go/quickstep/res/values/strings.xml
+++ b/go/quickstep/res/values/strings.xml
@@ -10,5 +10,5 @@
     <!-- Label for a button that translates a screenshot of the current app. [CHAR_LIMIT=40] -->
     <string name="action_translate">Translate</string>
     <!-- Label for a button that triggers Search on a screenshot of the current app. [CHAR_LIMIT=40] -->
-    <string name="action_search">Search</string>
+    <string name="action_search">Lens</string>
 </resources>
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index 36a4e7f..350e0d1 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -20,23 +20,17 @@
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
 import android.annotation.SuppressLint;
-import android.app.ActivityTaskManager;
-import android.app.IAssistDataReceiver;
 import android.app.assist.AssistContent;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
 import android.graphics.Matrix;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.text.TextUtils;
-import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.R;
+import com.android.quickstep.util.AssistContentRequester;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.systemui.shared.recents.model.Task;
@@ -68,16 +62,11 @@
      * @param <T> The type of View in which the overlay will be placed
      */
     public static final class TaskOverlayGo<T extends OverviewActionsView> extends TaskOverlay {
-        private static final String ASSIST_KEY_CONTENT = "content";
-
         private String mNIUPackageName;
-        private int mTaskId;
-        private Bundle mAssistData;
-        private final Handler mMainThreadHandler;
+        private String mWebUrl;
 
         private TaskOverlayGo(TaskThumbnailView taskThumbnailView) {
             super(taskThumbnailView);
-            mMainThreadHandler = new Handler(Looper.getMainLooper());
         }
 
         /**
@@ -95,34 +84,33 @@
             }
 
             getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
-            boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+            boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot();
             getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
 
-            mTaskId = task.key.id;
-            AssistDataReceiverImpl receiver = new AssistDataReceiverImpl();
-            receiver.setOverlay(this);
+            int taskId = task.key.id;
+            AssistContentRequester contentRequester =
+                    new AssistContentRequester(mApplicationContext);
+            contentRequester.requestAssistContent(taskId, this::onAssistContentReceived);
+        }
 
-            try {
-                ActivityTaskManager.getService().requestAssistDataForTask(receiver, mTaskId,
-                        mApplicationContext.getPackageName());
-            } catch (RemoteException e) {
-                Log.e(TAG, "Unable to request AssistData");
-            }
+        /** Provide Assist Content to the overlay. */
+        @VisibleForTesting
+        public void onAssistContentReceived(AssistContent assistContent) {
+            mWebUrl = assistContent.getWebUri() != null
+                    ? assistContent.getWebUri().toString() : null;
+        }
+
+        @Override
+        public void reset() {
+            super.reset();
+            mWebUrl = null;
         }
 
         /**
-         * Called when AssistDataReceiverImpl receives data from ActivityTaskManagerService's
-         * AssistDataRequester
+         * Creates and sends an Intent corresponding to the button that was clicked
          */
-        public void onAssistDataReceived(Bundle data) {
-            mMainThreadHandler.post(() -> {
-                if (data != null) {
-                    mAssistData = data;
-                }
-            });
-        }
-
-        private void sendNIUIntent(String actionType) {
+        @VisibleForTesting
+        public void sendNIUIntent(String actionType) {
             Intent intent = createNIUIntent(actionType);
             mImageApi.shareAsDataWithExplicitIntent(/* crop */ null, intent);
         }
@@ -134,12 +122,8 @@
                     .setPackage(mNIUPackageName)
                     .putExtra(ELAPSED_NANOS, SystemClock.elapsedRealtimeNanos());
 
-            if (mAssistData != null) {
-                final AssistContent content = mAssistData.getParcelable(ASSIST_KEY_CONTENT);
-                Uri webUri = (content == null) ? null : content.getWebUri();
-                if (webUri != null) {
-                    intent.putExtra(ACTIONS_URL, webUri.toString());
-                }
+            if (mWebUrl != null) {
+                intent.putExtra(ACTIONS_URL, mWebUrl);
             }
 
             return intent;
@@ -178,26 +162,11 @@
                 }
             }
         }
-    }
 
-    /**
-     * Basic AssistDataReceiver. This is passed to ActivityTaskManagerService, which then requests
-     * the data.
-     */
-    private static final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub {
-        private TaskOverlayGo mOverlay;
-
-        public void setOverlay(TaskOverlayGo overlay) {
-            mOverlay = overlay;
+        @VisibleForTesting
+        public void setImageActionsAPI(ImageActionsApi imageActionsApi) {
+            mImageApi = imageActionsApi;
         }
-
-        @Override
-        public void onHandleAssistData(Bundle data) {
-            mOverlay.onAssistDataReceived(data);
-        }
-
-        @Override
-        public void onHandleAssistScreenshot(Bitmap screenshot) {}
     }
 
     /**
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 34ff91d..c61610a 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -22,5 +22,4 @@
     android:layout_height="wrap_content"
     android:text="@string/recents_clear_all"
     android:textColor="?attr/workspaceTextColor"
-    android:textSize="14sp"
-    android:translationY="@dimen/task_thumbnail_half_top_margin" />
\ No newline at end of file
+    android:textSize="14sp" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 0f9a6aa..7e5b85c 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -13,6 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!-- NOTE! don't add dimensions for margins / paddings / sizes that change per orientation to this
+     file, they need to be loaded at runtime. -->
 <com.android.quickstep.views.TaskView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
@@ -24,8 +26,7 @@
     <com.android.quickstep.views.TaskThumbnailView
         android:id="@+id/snapshot"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginTop="@dimen/task_thumbnail_top_margin"/>
+        android:layout_height="match_parent"/>
 
     <com.android.quickstep.views.IconView
         android:id="@+id/icon"
diff --git a/quickstep/res/layout/taskbar_divider.xml b/quickstep/res/layout/taskbar_divider.xml
index 6e1aa1e..87649f7 100644
--- a/quickstep/res/layout/taskbar_divider.xml
+++ b/quickstep/res/layout/taskbar_divider.xml
@@ -18,6 +18,4 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="@dimen/taskbar_divider_thickness"
     android:layout_height="@dimen/taskbar_divider_height"
-    android:layout_marginStart="@dimen/taskbar_icon_spacing"
-    android:layout_marginEnd="@dimen/taskbar_icon_spacing"
     android:background="@color/taskbar_divider" />
\ No newline at end of file
diff --git a/quickstep/res/layout/taskbar_view.xml b/quickstep/res/layout/taskbar_view.xml
new file mode 100644
index 0000000..34a88ea
--- /dev/null
+++ b/quickstep/res/layout/taskbar_view.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<com.android.launcher3.taskbar.TaskbarView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/taskbar_view"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/taskbar_size"
+    android:background="@android:color/transparent"
+    android:layout_gravity="bottom"
+    android:gravity="center"
+    android:visibility="gone" />
+
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index c03eaa2..7cb01f6 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -16,4 +16,6 @@
 -->
 <resources>
     <dimen name="task_card_menu_horizontal_padding">24dp</dimen>
+
+    <dimen name="overview_task_margin">8dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 755bce8..3036341 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -15,11 +15,8 @@
 -->
 
 <resources>
-
-    <dimen name="task_thumbnail_top_margin">80dp</dimen>
-    <dimen name="task_thumbnail_half_top_margin">40dp</dimen>
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
-    <dimen name="task_icon_top_margin">16dp</dimen>
+    <dimen name="task_thumbnail_icon_size_grid">32dp</dimen>
     <!-- For screens without rounded corners -->
     <dimen name="task_corner_radius_small">2dp</dimen>
 
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 0764bb3..1603321 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -99,62 +99,61 @@
 
     <!-- Dismiss button string for search education view -->
     <string name="search_edu_dismiss">Got it.</string>
-
-    <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
-    <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>
-    <!-- Subtitle shown during interactive parts of Back gesture tutorial for right edge. [CHAR LIMIT=60] -->
-    <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge" translatable="false">Start at the right edge and swipe toward the middle</string>
-    <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_swipe_too_far_from_right_edge" translatable="false">Make sure you swipe from the far right edge</string>
-    <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_cancelled_right_edge" translatable="false">Make sure you swipe straight to the left and let go</string>
-
-    <!-- Title shown during interactive part of Back gesture tutorial for left edge. [CHAR LIMIT=30] -->
-    <string name="back_gesture_tutorial_playground_title_swipe_inward_left_edge" translatable="false">Try the other side</string>
-    <!-- Subtitle shown during interactive parts of Back gesture tutorial for left edge. [CHAR LIMIT=60] -->
-    <string name="back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge" translatable="false">That\'s it! Now try swiping from the left edge.</string>
     <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_swipe_too_far_from_left_edge" translatable="false">Make sure you swipe from the far left edge</string>
+    <string name="back_gesture_feedback_swipe_too_far_from_left_edge">Make sure you swipe from the far-left edge.</string>
     <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is cancelled. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_cancelled_left_edge" translatable="false">Make sure you swipe straight to the right and let go</string>
-
+    <string name="back_gesture_feedback_cancelled_left_edge">Make sure you swipe from the left edge to the middle of the screen and let go.</string>
+    <!-- Feedback shown after completing the left back gesture before continuing on to the right edge. [CHAR LIMIT=60] -->
+    <string name="back_gesture_feedback_complete_left_edge">That\'s it! Now try swiping from the right edge.</string>
+    <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is too far from the edge. [CHAR LIMIT=100] -->
+    <string name="back_gesture_feedback_swipe_too_far_from_right_edge">Make sure you swipe from the far-right edge.</string>
+    <!-- Feedback shown during interactive parts of Back gesture tutorial for right edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+    <string name="back_gesture_feedback_cancelled_right_edge">Make sure you swipe from the right edge to the middle of the screen and let go.</string>
+    <!-- Feedback shown during interactive parts of Back gesture tutorial for left edge when the gesture is cancelled. [CHAR LIMIT=100] -->
+    <string name="back_gesture_feedback_complete">You completed the go back gesture. Next up, learn how to go Home.</string>
     <!-- Feedback shown during interactive parts of Back gesture tutorial when the gesture is within the nav bar region. [CHAR LIMIT=100] -->
-    <string name="back_gesture_feedback_swipe_in_nav_bar" translatable="false">Make sure you don\'t swipe too close to the bottom of the screen</string>
+    <string name="back_gesture_feedback_swipe_in_nav_bar">Make sure you don\'t swipe too close to the bottom of the screen.</string>
     <!-- Subtitle shown on the confirmation screen after successful gesture. [CHAR LIMIT=60] -->
-    <string name="back_gesture_tutorial_confirm_subtitle" translatable="false">To change the sensitivity of the back gesture, go to Settings</string>
-
-    <!-- Title shown during interactive part of Home gesture tutorial. [CHAR LIMIT=30] -->
-    <string name="home_gesture_tutorial_playground_title" translatable="false">Tutorial: Go Home</string>
-    <!-- Subtitle shown during interactive parts of Home gesture tutorial. [CHAR LIMIT=60] -->
-    <string name="home_gesture_tutorial_playground_subtitle" translatable="false">Try swiping upward from the bottom edge of the screen</string>
+    <string name="back_gesture_tutorial_confirm_subtitle">To change the sensitivity of the back gesture, go to Settings</string>
     <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
-    <string name="home_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
-    <!-- Feedback shown during interactive parts of Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
-    <string name="home_gesture_feedback_overview_detected" translatable="false">Make sure you don\'t pause before letting go</string>
-    <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
-    <string name="home_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up</string>
+    <!-- Introduction title for the Back gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="back_gesture_intro_title">Swipe to go back</string>
+    <!-- Introduction subtitle for the Back gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="back_gesture_intro_subtitle">To go back to the last screen, swipe from the left or right edge to the middle of the screen.</string>
 
-    <!-- Title shown during interactive part of Overview gesture tutorial. [CHAR LIMIT=30] -->
-    <string name="overview_gesture_tutorial_playground_title" translatable="false">Tutorial: Switch Apps</string>
-    <!-- Subtitle shown during interactive parts of Overview gesture tutorial. [CHAR LIMIT=60] -->
-    <string name="overview_gesture_tutorial_playground_subtitle" translatable="false">Swipe up from the bottom of the screen and hold</string>
+    <string name="home_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe up from the bottom edge of the screen.</string>
+    <!-- Feedback shown during interactive parts of Home gesture tutorial when the Overview gesture is detected. [CHAR LIMIT=100] -->
+    <string name="home_gesture_feedback_overview_detected">Make sure you don\'t pause before letting go.</string>
+    <!-- Feedback shown during interactive parts of Home gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
+    <string name="home_gesture_feedback_wrong_swipe_direction">Make sure you swipe straight up.</string>
+    <string name="home_gesture_feedback_complete">You completed the go Home gesture. Next up, learn how to switch apps.</string>
+    <!-- Introduction title for the Home gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="home_gesture_intro_title">Swipe to go home</string>
+    <!-- Introduction subtitle for the Home gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="home_gesture_intro_subtitle">Swipe up from the bottom of your screen. This gesture always takes you to the Home screen.</string>
+
     <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is started too far from the edge. [CHAR LIMIT=100] -->
-    <string name="overview_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the bottom edge of the screen</string>
+    <string name="overview_gesture_feedback_swipe_too_far_from_edge">Make sure you swipe up from the bottom edge of the screen.</string>
     <!-- Feedback shown during interactive parts of Overview gesture tutorial when the Home gesture is detected. [CHAR LIMIT=100] -->
-    <string name="overview_gesture_feedback_home_detected" translatable="false">Try holding the window for longer before releasing</string>
+    <string name="overview_gesture_feedback_home_detected">Try holding the window for longer before releasing.</string>
     <!-- Feedback shown during interactive parts of Overview gesture tutorial when the gesture is horizontal instead of vertical. [CHAR LIMIT=100] -->
-    <string name="overview_gesture_feedback_wrong_swipe_direction" translatable="false">Make sure you swipe straight up and pause</string>
+    <string name="overview_gesture_feedback_wrong_swipe_direction">Make sure you swipe straight up, then pause.</string>
+    <string name="overview_gesture_feedback_complete">You completed the switch apps gesture. You\'re ready to use your phone!</string>
+    <!-- Introduction title for the Overview gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="overview_gesture_intro_title">Swipe to switch apps</string>
+    <!-- Introduction subtitle for the Overview gesture tutorial. [CHAR LIMIT=100] -->
+    <string name="overview_gesture_intro_subtitle">Swipe up from the bottom of your screen, hold, then release.</string>
 
     <!-- Title shown during interactive part of Assistant gesture tutorial. [CHAR LIMIT=30] -->
     <string name="assistant_gesture_tutorial_playground_title" translatable="false">Tutorial: Assistant</string>
     <!-- Subtitle shown during interactive parts of Assistant gesture tutorial. [CHAR LIMIT=60] -->
     <string name="assistant_gesture_tutorial_playground_subtitle" translatable="false">Try swiping diagonally from a bottom corner of the screen</string>
     <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture is started too far from the corner. [CHAR LIMIT=100] -->
-    <string name="assistant_gesture_feedback_swipe_too_far_from_corner" translatable="false">Make sure you swipe from a bottom corner of the screen</string>
+    <string name="assistant_gesture_feedback_swipe_too_far_from_corner" translatable="false">Make sure you swipe from a bottom corner of the screen.</string>
     <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go diagonally enough. [CHAR LIMIT=100] -->
-    <string name="assistant_gesture_feedback_swipe_not_diagonal" translatable="false">Make sure you swipe diagonally</string>
+    <string name="assistant_gesture_feedback_swipe_not_diagonal" translatable="false">Make sure you swipe diagonally.</string>
     <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go far enough. [CHAR LIMIT=100] -->
-    <string name="assistant_gesture_feedback_swipe_not_long_enough" translatable="false">Try swiping further</string>
+    <string name="assistant_gesture_feedback_swipe_not_long_enough" translatable="false">Try swiping further.</string>
 
     <!-- Title shown in sandbox mode part of gesture tutorial. [CHAR LIMIT=30] -->
     <string name="sandbox_mode_title" translatable="false">Sandbox Mode</string>
@@ -172,11 +171,19 @@
     <string name="sandbox_mode_back_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the left/right edge of the screen</string>
 
     <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
-    <string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
-    <!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
-    <string name="gesture_tutorial_action_button_label_done" translatable="false">Done</string>
+    <string name="gesture_tutorial_confirm_title">All set</string>
+    <!-- Button text shown on a button on the feedback popup to proceed to the next tutorial step. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_next">Next</string>
+    <!-- Button text shown on a button on the feedback popup to complete the tutorial. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_done">Done</string>
     <!-- Button text shown on a button to go to Settings. [CHAR LIMIT=14] -->
-    <string name="gesture_tutorial_action_button_label_settings" translatable="false">Settings</string>
+    <string name="gesture_tutorial_action_button_label_settings">Settings</string>
+    <!-- Feedback title to try again. [CHAR LIMIT=30] -->
+    <string name="gesture_tutorial_try_again">Try again</string>
+    <!-- Feedback title for a successful gesture. [CHAR LIMIT=30] -->
+    <string name="gesture_tutorial_nice">Nice!</string>
+    <!-- Feedback subtext displaying the current step and the total number of steps for the tutorial. [CHAR LIMIT=30] -->
+    <string name="gesture_tutorial_step">Tutorial <xliff:g id="current">%1$d</xliff:g>/<xliff:g id="total">%2$d</xliff:g></string>
 
     <!-- ******* Overview ******* -->
     <!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
@@ -185,4 +192,14 @@
     <string name="action_screenshot">Screenshot</string>
     <!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] -->
     <string name="blocked_by_policy">This action isn\'t allowed by the app or your organization</string>
+
+    <!-- ******* Skip tutorial dialog ******* -->
+    <!-- Title for the dialog that allows the user to skip the gesture navigation tutorial. [CHAR_LIMIT=40] -->
+    <string name="skip_tutorial_dialog_title">Skip navigation tutorial?</string>
+    <!-- Subtitle for the dialog that allows the user to skip the gesture navigation tutorial. [CHAR_LIMIT=40] -->
+    <string name="skip_tutorial_dialog_subtitle">You can find this later in the Tips app</string>
+    <!-- Button text shown on a button on the tutorial skip dialog to return to the tutorial. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_cancel">Cancel</string>
+    <!-- Button text shown on a button on the tutorial skip dialog to exit the tutorial. [CHAR LIMIT=14] -->
+    <string name="gesture_tutorial_action_button_label_skip">Skip</string>
 </resources>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index c4becf1..085db6d 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarController;
 import com.android.launcher3.taskbar.TaskbarStateHandler;
+import com.android.launcher3.taskbar.TaskbarView;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.DisplayController;
@@ -243,9 +244,10 @@
             mTaskbarController = null;
         }
         if (mDeviceProfile.isTaskbarPresent) {
+            TaskbarView taskbarViewOnHome = (TaskbarView) mHotseat.getTaskbarView();
             TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this);
             mTaskbarController = new TaskbarController(this,
-                    taskbarActivityContext.getTaskbarContainerView());
+                    taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome);
             mTaskbarController.init();
         }
     }
@@ -334,12 +336,16 @@
     }
 
     @Override
+    public float getNormalTaskbarScale() {
+        if (mTaskbarController != null) {
+            return mTaskbarController.getTaskbarScaleOnHome();
+        }
+        return super.getNormalTaskbarScale();
+    }
+
+    @Override
     public void onDragLayerHierarchyChanged() {
         onLauncherStateOrFocusChanged();
-
-        if (mTaskbarController != null) {
-            mTaskbarController.onLauncherDragLayerHierarchyChanged();
-        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 64b22d4..82a83fc 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -68,6 +68,7 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.util.ActivityOptionsWrapper;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8312b82..5513c16 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -35,14 +35,13 @@
     private final DeviceProfile mDeviceProfile;
     private final LayoutInflater mLayoutInflater;
     private final TaskbarContainerView mTaskbarContainerView;
-    private final float mIconScale;
 
     public TaskbarActivityContext(BaseQuickstepLauncher launcher) {
         super(launcher);
         mDeviceProfile = launcher.getDeviceProfile().copy(this);
         float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size);
-        mIconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
-        mDeviceProfile.updateIconSize(mIconScale, getResources());
+        float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx;
+        mDeviceProfile.updateIconSize(iconScale, getResources());
 
         mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
 
@@ -54,10 +53,7 @@
         return mTaskbarContainerView;
     }
 
-    /**
-     * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will
-     * be able to access this TaskbarActivityContext via ActivityContext.lookupContext().
-     */
+    @Override
     public LayoutInflater getLayoutInflater() {
         return mLayoutInflater;
     }
@@ -76,11 +72,4 @@
     public Rect getFolderBoundingBox() {
         return mTaskbarContainerView.getFolderBoundingBox();
     }
-
-    /**
-     * @return The ratio of taskbar icon size vs normal workspace/hotseat icon size.
-     */
-    public float getTaskbarIconScale() {
-        return mIconScale;
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
similarity index 72%
rename from quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
rename to quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
index 2228eba..7c54e2d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java
@@ -20,36 +20,38 @@
 import android.animation.Animator;
 
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Utilities;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
 import com.android.systemui.shared.system.QuickStepContract;
 
 /**
- * Works with TaskbarController to update the TaskbarView's alpha based on LauncherState, whether
- * Launcher is in the foreground, etc.
+ * Works with TaskbarController to update the TaskbarView's visual properties based on factors such
+ * as LauncherState, whether Launcher is in the foreground, etc.
  */
-public class TaskbarVisibilityController {
+public class TaskbarAnimationController {
 
     private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
-    private static final long FLOATING_VIEW_VISIBILITY_ALPHA_DURATION = 120;
 
     private final BaseQuickstepLauncher mLauncher;
-    private final TaskbarController.TaskbarVisibilityControllerCallbacks mTaskbarCallbacks;
+    private final TaskbarController.TaskbarAnimationControllerCallbacks mTaskbarCallbacks;
 
     // Background alpha.
-    private AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
+    private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat(
             this::onTaskbarBackgroundAlphaChanged);
 
     // Overall visibility.
-    private AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
+    private final AnimatedFloat mTaskbarVisibilityAlphaForLauncherState = new AnimatedFloat(
             this::updateVisibilityAlpha);
-    private AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
-            this::updateVisibilityAlpha);
-    private AnimatedFloat mTaskbarVisibilityAlphaForFloatingView = new AnimatedFloat(
+    private final AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
             this::updateVisibilityAlpha);
 
-    public TaskbarVisibilityController(BaseQuickstepLauncher launcher,
-            TaskbarController.TaskbarVisibilityControllerCallbacks taskbarCallbacks) {
+    // Scale.
+    private final AnimatedFloat mTaskbarScaleForLauncherState = new AnimatedFloat(
+            this::updateScale);
+
+    public TaskbarAnimationController(BaseQuickstepLauncher launcher,
+            TaskbarController.TaskbarAnimationControllerCallbacks taskbarCallbacks) {
         mLauncher = launcher;
         mTaskbarCallbacks = taskbarCallbacks;
     }
@@ -62,7 +64,6 @@
         boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
                 & QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
         mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
-        mTaskbarVisibilityAlphaForFloatingView.updateValue(1f);
 
         onTaskbarBackgroundAlphaChanged();
         updateVisibilityAlpha();
@@ -76,6 +77,10 @@
         return mTaskbarVisibilityAlphaForLauncherState;
     }
 
+    protected AnimatedFloat getTaskbarScaleForLauncherState() {
+        return mTaskbarScaleForLauncherState;
+    }
+
     protected Animator createAnimToBackgroundAlpha(float toAlpha, long duration) {
         return mTaskbarBackgroundAlpha.animateToValue(mTaskbarBackgroundAlpha.value, toAlpha)
                 .setDuration(duration);
@@ -86,14 +91,10 @@
                 .setDuration(IME_VISIBILITY_ALPHA_DURATION).start();
     }
 
-    protected void animateToVisibilityForFloatingView(float toAlpha) {
-        mTaskbarVisibilityAlphaForIme.animateToValue(mTaskbarVisibilityAlphaForFloatingView.value,
-                toAlpha).setDuration(FLOATING_VIEW_VISIBILITY_ALPHA_DURATION).start();
-    }
-
     private void onTaskbarBackgroundAlphaChanged() {
         mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
         updateVisibilityAlpha();
+        updateScale();
     }
 
     private void updateVisibilityAlpha() {
@@ -102,8 +103,7 @@
         // LauncherState if Launcher is paused.
         float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
                 mTaskbarVisibilityAlphaForLauncherState.value);
-        float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value
-                * mTaskbarVisibilityAlphaForFloatingView.value;
+        float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value;
         float taskbarAlpha = alphaDueToLauncher * alphaDueToOther;
         mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
 
@@ -111,6 +111,15 @@
         setNavBarButtonAlpha(1f - taskbarAlpha);
     }
 
+    private void updateScale() {
+        // We use mTaskbarBackgroundAlpha as a proxy for whether Launcher is resumed/paused, the
+        // assumption being that Taskbar should always be at scale 1f regardless of the current
+        // LauncherState if Launcher is paused.
+        float scale = mTaskbarScaleForLauncherState.value;
+        scale = Utilities.mapRange(mTaskbarBackgroundAlpha.value, scale, 1f);
+        mTaskbarCallbacks.updateTaskbarScale(scale);
+    }
+
     private void setNavBarButtonAlpha(float navBarAlpha) {
         SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
index 528f43e..5202d91 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java
@@ -84,7 +84,7 @@
     private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() {
         return insetsInfo -> {
             if (getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD
-                    || mTaskbarView.isDraggingItem()) {
+                    || mTaskbarView.getVisibility() != VISIBLE || mTaskbarView.isDraggingItem()) {
                 // We're invisible or dragging out of taskbar, let touches pass through us.
                 insetsInfo.touchableRegion.setEmpty();
                 insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index f652961..de23ad2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,10 +19,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_TASKBAR;
-import static com.android.launcher3.AbstractFloatingView.TYPE_REPLACE_TASKBAR_WITH_HOTSEAT;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
 import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR;
 
@@ -43,7 +39,6 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
@@ -54,6 +49,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.AnimatedFloat;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -70,13 +66,14 @@
     private static final String WINDOW_TITLE = "Taskbar";
 
     private final TaskbarContainerView mTaskbarContainerView;
-    private final TaskbarView mTaskbarView;
+    private final TaskbarView mTaskbarViewInApp;
+    private final TaskbarView mTaskbarViewOnHome;
     private final BaseQuickstepLauncher mLauncher;
     private final WindowManager mWindowManager;
     // Layout width and height of the Taskbar in the default state.
     private final Point mTaskbarSize;
     private final TaskbarStateHandler mTaskbarStateHandler;
-    private final TaskbarVisibilityController mTaskbarVisibilityController;
+    private final TaskbarAnimationController mTaskbarAnimationController;
     private final TaskbarHotseatController mHotseatController;
     private final TaskbarRecentsController mRecentsController;
     private final TaskbarDragController mDragController;
@@ -91,19 +88,22 @@
 
     private @Nullable Animator mAnimator;
     private boolean mIsAnimatingToLauncher;
+    private boolean mIsAnimatingToApp;
 
     public TaskbarController(BaseQuickstepLauncher launcher,
-            TaskbarContainerView taskbarContainerView) {
+            TaskbarContainerView taskbarContainerView, TaskbarView taskbarViewOnHome) {
         mLauncher = launcher;
         mTaskbarContainerView = taskbarContainerView;
         mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks());
-        mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view);
-        mTaskbarView.construct(createTaskbarViewCallbacks());
+        mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view);
+        mTaskbarViewInApp.construct(createTaskbarViewCallbacks());
+        mTaskbarViewOnHome = taskbarViewOnHome;
+        mTaskbarViewOnHome.construct(createTaskbarViewCallbacks());
         mWindowManager = mLauncher.getWindowManager();
         mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize);
         mTaskbarStateHandler = mLauncher.getTaskbarStateHandler();
-        mTaskbarVisibilityController = new TaskbarVisibilityController(mLauncher,
-                createTaskbarVisibilityControllerCallbacks());
+        mTaskbarAnimationController = new TaskbarAnimationController(mLauncher,
+                createTaskbarAnimationControllerCallbacks());
         mHotseatController = new TaskbarHotseatController(mLauncher,
                 createTaskbarHotseatControllerCallbacks());
         mRecentsController = new TaskbarRecentsController(mLauncher,
@@ -111,16 +111,23 @@
         mDragController = new TaskbarDragController(mLauncher);
     }
 
-    private TaskbarVisibilityControllerCallbacks createTaskbarVisibilityControllerCallbacks() {
-        return new TaskbarVisibilityControllerCallbacks() {
+    private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() {
+        return new TaskbarAnimationControllerCallbacks() {
             @Override
             public void updateTaskbarBackgroundAlpha(float alpha) {
-                mTaskbarView.setBackgroundAlpha(alpha);
+                mTaskbarViewInApp.setBackgroundAlpha(alpha);
             }
 
             @Override
             public void updateTaskbarVisibilityAlpha(float alpha) {
                 mTaskbarContainerView.setAlpha(alpha);
+                mTaskbarViewOnHome.setAlpha(alpha);
+            }
+
+            @Override
+            public void updateTaskbarScale(float scale) {
+                mTaskbarViewInApp.setScaleX(scale);
+                mTaskbarViewInApp.setScaleY(scale);
             }
         };
     }
@@ -148,13 +155,22 @@
                         ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
                                 ActivityOptions.makeBasic());
                     } else if (tag instanceof FolderInfo) {
-                        if (mLauncher.hasBeenResumed()) {
-                            FolderInfo folderInfo = (FolderInfo) tag;
-                            onClickedOnFolderFromHome(folderInfo);
-                        } else {
-                            FolderIcon folderIcon = (FolderIcon) view;
-                            onClickedOnFolderInApp(folderIcon);
-                        }
+                        FolderIcon folderIcon = (FolderIcon) view;
+                        Folder folder = folderIcon.getFolder();
+
+                        setTaskbarWindowFullscreen(true);
+
+                        mTaskbarContainerView.post(() -> {
+                            folder.animateOpen();
+
+                            folder.iterateOverItems((itemInfo, itemView) -> {
+                                itemView.setOnClickListener(getItemOnClickListener());
+                                itemView.setOnLongClickListener(getItemOnLongClickListener());
+                                // To play haptic when dragging, like other Taskbar items do.
+                                itemView.setHapticFeedbackEnabled(true);
+                                return false;
+                            });
+                        });
                     } else {
                         ItemClickHandler.INSTANCE.onClick(view);
                     }
@@ -164,52 +180,30 @@
                 };
             }
 
-            // Open the real folder in Launcher.
-            private void onClickedOnFolderFromHome(FolderInfo folderInfo) {
-                alignRealHotseatWithTaskbar();
-
-                FolderIcon folderIcon = (FolderIcon) mLauncher.getHotseat()
-                        .getFirstItemMatch((info, v) -> info == folderInfo);
-                folderIcon.post(folderIcon::performClick);
-            }
-
-            // Open the Taskbar folder, and handle clicks on folder items.
-            private void onClickedOnFolderInApp(FolderIcon folderIcon) {
-                Folder folder = folderIcon.getFolder();
-
-                setTaskbarWindowFullscreen(true);
-
-                mTaskbarContainerView.post(() -> {
-                    folder.animateOpen();
-
-                    folder.iterateOverItems((itemInfo, itemView) -> {
-                        itemView.setOnClickListener(getItemOnClickListener());
-                        itemView.setOnLongClickListener(getItemOnLongClickListener());
-                        // To play haptic when dragging, like other Taskbar items do.
-                        itemView.setHapticFeedbackEnabled(true);
-                        return false;
-                    });
-                });
-            }
-
             @Override
             public View.OnLongClickListener getItemOnLongClickListener() {
-                return view -> {
-                    if (mLauncher.hasBeenResumed() && view.getTag() instanceof ItemInfo) {
-                        alignRealHotseatWithTaskbar();
-                        return mDragController.startWorkspaceDragOnLongClick(view);
-                    } else {
-                        return mDragController.startSystemDragOnLongClick(view);
-                    }
-                };
+                return mDragController::startSystemDragOnLongClick;
             }
 
             @Override
-            public int getEmptyHotseatViewVisibility() {
+            public int getEmptyHotseatViewVisibility(TaskbarView taskbarView) {
                 // When on the home screen, we want the empty hotseat views to take up their full
                 // space so that the others line up with the home screen hotseat.
-                return mLauncher.hasBeenResumed() || mIsAnimatingToLauncher
-                        ? View.INVISIBLE : View.GONE;
+                boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome
+                        || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher;
+                return isOnHomeScreen ? View.INVISIBLE : View.GONE;
+            }
+
+            @Override
+            public float getNonIconScale(TaskbarView taskbarView) {
+                return taskbarView == mTaskbarViewOnHome ? getTaskbarScaleOnHome() : 1f;
+            }
+
+            @Override
+            public void onItemPositionsChanged(TaskbarView taskbarView) {
+                if (taskbarView == mTaskbarViewOnHome) {
+                    alignRealHotseatWithTaskbar();
+                }
             }
         };
     }
@@ -218,7 +212,7 @@
         return new TaskbarHotseatControllerCallbacks() {
             @Override
             public void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
-                mTaskbarView.updateHotseatItems(hotseatItemInfos);
+                mTaskbarViewInApp.updateHotseatItems(hotseatItemInfos);
                 mLatestLoadedHotseatItems = hotseatItemInfos;
                 dedupeAndUpdateRecentItems();
             }
@@ -235,7 +229,8 @@
 
             @Override
             public void updateRecentTaskAtIndex(int taskIndex, Task task) {
-                mTaskbarView.updateRecentTaskAtIndex(taskIndex, task);
+                mTaskbarViewInApp.updateRecentTaskAtIndex(taskIndex, task);
+                mTaskbarViewOnHome.updateRecentTaskAtIndex(taskIndex, task);
             }
         };
     }
@@ -244,23 +239,30 @@
      * Initializes the Taskbar, including adding it to the screen.
      */
     public void init() {
-        mTaskbarView.init(mHotseatController.getNumHotseatIcons(),
+        mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(),
                 mRecentsController.getNumRecentIcons());
-        mTaskbarContainerView.init(mTaskbarView);
+        mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(),
+                mRecentsController.getNumRecentIcons());
+        mTaskbarContainerView.init(mTaskbarViewInApp);
         addToWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks());
-        mTaskbarVisibilityController.init();
+        mTaskbarAnimationController.init();
         mHotseatController.init();
         mRecentsController.init();
 
-        SCALE_PROPERTY.set(mTaskbarView, mLauncher.hasBeenResumed() ? getTaskbarScaleOnHome() : 1f);
+        updateWhichTaskbarViewIsVisible();
     }
 
     private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() {
         return new TaskbarStateHandlerCallbacks() {
             @Override
             public AnimatedFloat getAlphaTarget() {
-                return mTaskbarVisibilityController.getTaskbarVisibilityForLauncherState();
+                return mTaskbarAnimationController.getTaskbarVisibilityForLauncherState();
+            }
+
+            @Override
+            public AnimatedFloat getScaleTarget() {
+                return mTaskbarAnimationController.getTaskbarScaleForLauncherState();
             }
         };
     }
@@ -274,11 +276,12 @@
             mAnimator.end();
         }
 
-        mTaskbarView.cleanup();
+        mTaskbarViewInApp.cleanup();
+        mTaskbarViewOnHome.cleanup();
         mTaskbarContainerView.cleanup();
         removeFromWindowManager();
         mTaskbarStateHandler.setTaskbarCallbacks(null);
-        mTaskbarVisibilityController.cleanup();
+        mTaskbarAnimationController.cleanup();
         mHotseatController.cleanup();
         mRecentsController.cleanup();
     }
@@ -313,7 +316,7 @@
         TaskbarContainerView.LayoutParams taskbarLayoutParams =
                 new TaskbarContainerView.LayoutParams(mTaskbarSize.x, mTaskbarSize.y);
         taskbarLayoutParams.gravity = gravity;
-        mTaskbarView.setLayoutParams(taskbarLayoutParams);
+        mTaskbarViewInApp.setLayoutParams(taskbarLayoutParams);
 
         mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams);
     }
@@ -330,7 +333,6 @@
             mAnimator = createAnimToLauncher(null, duration);
         } else {
             mAnimator = createAnimToApp(duration);
-            replaceTaskbarWithHotseatOrViceVersa();
         }
         mAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -347,40 +349,42 @@
      */
     public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) {
         PendingAnimation anim = new PendingAnimation(duration);
-        anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(0, duration));
+        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration));
         if (toState != null) {
             mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim);
         }
-        anim.addFloat(mTaskbarView, SCALE_PROPERTY, mTaskbarView.getScaleX(),
-                getTaskbarScaleOnHome(), LINEAR);
 
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
                 mIsAnimatingToLauncher = true;
-                mTaskbarView.updateHotseatItemsVisibility();
+                mTaskbarViewInApp.updateHotseatItemsVisibility();
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
                 mIsAnimatingToLauncher = false;
+                updateWhichTaskbarViewIsVisible();
             }
         });
 
-        anim.addOnFrameCallback(this::alignRealHotseatWithTaskbar);
-
         return anim.buildAnim();
     }
 
     private Animator createAnimToApp(long duration) {
         PendingAnimation anim = new PendingAnimation(duration);
-        anim.add(mTaskbarVisibilityController.createAnimToBackgroundAlpha(1, duration));
-        anim.addFloat(mTaskbarView, SCALE_PROPERTY, mTaskbarView.getScaleX(), 1f, LINEAR);
+        anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration));
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                mTaskbarView.updateHotseatItemsVisibility();
-                setReplaceTaskbarWithHotseat(false);
+                mIsAnimatingToApp = true;
+                mTaskbarViewInApp.updateHotseatItemsVisibility();
+                updateWhichTaskbarViewIsVisible();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mIsAnimatingToApp = false;
             }
         });
         return anim.buildAnim();
@@ -390,7 +394,7 @@
      * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly.
      */
     public void setIsImeVisible(boolean isImeVisible) {
-        mTaskbarVisibilityController.animateToVisibilityForIme(isImeVisible ? 0 : 1);
+        mTaskbarAnimationController.animateToVisibilityForIme(isImeVisible ? 0 : 1);
     }
 
     /**
@@ -405,11 +409,11 @@
      * @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
      */
     public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
-        return mTaskbarView.isEventOverAnyItem(ev);
+        return mTaskbarViewInApp.isEventOverAnyItem(ev);
     }
 
     public boolean isDraggingItem() {
-        return mTaskbarView.isDraggingItem();
+        return mTaskbarViewInApp.isDraggingItem() || mTaskbarViewOnHome.isDraggingItem();
     }
 
     private void dedupeAndUpdateRecentItems() {
@@ -454,7 +458,8 @@
             tasksArray[tasksArray.length - 1 - i] = task;
         }
 
-        mTaskbarView.updateRecentTasks(tasksArray);
+        mTaskbarViewInApp.updateRecentTasks(tasksArray);
+        mTaskbarViewOnHome.updateRecentTasks(tasksArray);
         mRecentsController.loadIconsForTasks(tasksArray);
     }
 
@@ -475,47 +480,36 @@
         int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize;
         int hotseatTopDiff = hotseatHeight - grid.taskbarSize;
 
-        mTaskbarView.getHotseatBoundsAtScale(getTaskbarScaleOnHome()).roundOut(hotseatBounds);
-        mLauncher.getHotseat().setPadding(hotseatBounds.left, hotseatBounds.top + hotseatTopDiff,
-                mTaskbarView.getWidth() - hotseatBounds.right,
-                mTaskbarView.getHeight() - hotseatBounds.bottom);
+        mTaskbarViewOnHome.getHotseatBounds().roundOut(hotseatBounds);
+        mLauncher.getHotseat().setPadding(hotseatBounds.left,
+                hotseatBounds.top + hotseatTopDiff,
+                mTaskbarViewOnHome.getWidth() - hotseatBounds.right,
+                mTaskbarViewOnHome.getHeight() - hotseatBounds.bottom);
+    }
+
+    private void updateWhichTaskbarViewIsVisible() {
+        boolean isInApp = !mLauncher.hasBeenResumed() || mIsAnimatingToLauncher
+                || mIsAnimatingToApp;
+        if (isInApp) {
+            mTaskbarViewInApp.setVisibility(View.VISIBLE);
+            mTaskbarViewOnHome.setVisibility(View.INVISIBLE);
+            mLauncher.getHotseat().setIconsAlpha(0);
+        } else {
+            mTaskbarViewInApp.setVisibility(View.INVISIBLE);
+            mTaskbarViewOnHome.setVisibility(View.VISIBLE);
+            mLauncher.getHotseat().setIconsAlpha(1);
+        }
     }
 
     /**
-     * A view was added or removed from DragLayer, check if we need to hide our hotseat copy and
-     * show the real one instead.
+     * Returns the ratio of the taskbar icon size on home vs in an app.
      */
-    public void onLauncherDragLayerHierarchyChanged() {
-        replaceTaskbarWithHotseatOrViceVersa();
-    }
-
-    private void replaceTaskbarWithHotseatOrViceVersa() {
-        boolean replaceTaskbarWithHotseat = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
-                TYPE_REPLACE_TASKBAR_WITH_HOTSEAT) != null;
-        if (!mLauncher.hasBeenResumed()) {
-            replaceTaskbarWithHotseat = false;
-        }
-        setReplaceTaskbarWithHotseat(replaceTaskbarWithHotseat);
-
-        boolean hideTaskbar = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
-                TYPE_HIDE_TASKBAR) != null;
-        mTaskbarVisibilityController.animateToVisibilityForFloatingView(hideTaskbar ? 0f : 1f);
-    }
-
-    private void setReplaceTaskbarWithHotseat(boolean replaceTaskbarWithHotseat) {
-        Hotseat hotseat = mLauncher.getHotseat();
-        if (replaceTaskbarWithHotseat) {
-            alignRealHotseatWithTaskbar();
-            hotseat.getReplaceTaskbarAlpha().setValue(1f);
-            mTaskbarView.setHotseatViewsHidden(true);
-        } else {
-            hotseat.getReplaceTaskbarAlpha().setValue(0f);
-            mTaskbarView.setHotseatViewsHidden(false);
-        }
-    }
-
-    private float getTaskbarScaleOnHome() {
-        return 1f / mTaskbarContainerView.getTaskbarActivityContext().getTaskbarIconScale();
+    public float getTaskbarScaleOnHome() {
+        DeviceProfile inAppDp = mTaskbarContainerView.getTaskbarActivityContext()
+                .getDeviceProfile();
+        DeviceProfile onHomeDp = ActivityContext.lookupContext(mTaskbarViewOnHome.getContext())
+                .getDeviceProfile();
+        return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx;
     }
 
     /**
@@ -537,15 +531,17 @@
      */
     protected interface TaskbarStateHandlerCallbacks {
         AnimatedFloat getAlphaTarget();
+        AnimatedFloat getScaleTarget();
     }
 
     /**
-     * Contains methods that TaskbarVisibilityController can call to interface with
+     * Contains methods that TaskbarAnimationController can call to interface with
      * TaskbarController.
      */
-    protected interface TaskbarVisibilityControllerCallbacks {
+    protected interface TaskbarAnimationControllerCallbacks {
         void updateTaskbarBackgroundAlpha(float alpha);
         void updateTaskbarVisibilityAlpha(float alpha);
+        void updateTaskbarScale(float scale);
     }
 
     /**
@@ -561,7 +557,10 @@
     protected interface TaskbarViewCallbacks {
         View.OnClickListener getItemOnClickListener();
         View.OnLongClickListener getItemOnLongClickListener();
-        int getEmptyHotseatViewVisibility();
+        int getEmptyHotseatViewVisibility(TaskbarView taskbarView);
+        /** Returns how much to scale non-icon elements such as spacing and dividers. */
+        float getNonIconScale(TaskbarView taskbarView);
+        void onItemPositionsChanged(TaskbarView taskbarView);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index f51e498..5eb34cb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -33,7 +33,6 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ClipDescriptionCompat;
@@ -126,38 +125,6 @@
     }
 
     /**
-     * Starts a drag and drop operation that controls a real Workspace (Hotseat) view.
-     * @param view The Taskbar item that was long clicked.
-     * @return Whether {@link View#startDragAndDrop} started successfully.
-     */
-    protected boolean startWorkspaceDragOnLongClick(View view) {
-        View.DragShadowBuilder transparentShadowBuilder = new View.DragShadowBuilder(view) {
-            private static final int ARBITRARY_SHADOW_SIZE = 10;
-
-            @Override
-            public void onDrawShadow(Canvas canvas) {
-            }
-
-            @Override
-            public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) {
-                outShadowSize.set(ARBITRARY_SHADOW_SIZE, ARBITRARY_SHADOW_SIZE);
-                outShadowTouchPoint.set(ARBITRARY_SHADOW_SIZE / 2, ARBITRARY_SHADOW_SIZE / 2);
-            }
-        };
-
-        TaskbarDragListener taskbarDragListener = new TaskbarDragListener(mLauncher,
-                (ItemInfo) view.getTag());
-        if (view.startDragAndDrop(new ClipData("", new String[] {taskbarDragListener.getMimeType()},
-                        new ClipData.Item("")),
-                transparentShadowBuilder, null /* localState */, View.DRAG_FLAG_GLOBAL)) {
-            view.setOnDragListener(getDraggedViewDragListener());
-            taskbarDragListener.init(mLauncher.getDragLayer());
-            return true;
-        }
-        return false;
-    }
-
-    /**
      * Hide the original Taskbar item while it is being dragged.
      */
     private View.OnDragListener getDraggedViewDragListener() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
deleted file mode 100644
index dc27df1..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragListener.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2021 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.taskbar;
-
-import android.content.ClipDescription;
-import android.graphics.Point;
-import android.view.DragEvent;
-import android.view.View;
-
-import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.ItemInfo;
-
-import java.util.UUID;
-
-/**
- * Listens to system drag and drop events initated by the Taskbar, and forwards them to Launcher's
- * internal DragController to move Hotseat items.
- */
-public class TaskbarDragListener implements View.OnDragListener {
-
-    private static final String MIME_TYPE_PREFIX = "com.android.launcher3.taskbar.drag_and_drop/";
-
-    private final BaseQuickstepLauncher mLauncher;
-    private final ItemInfo mDraggedItem;
-    // Randomly generated id used to verify the drag event.
-    private final String mId;
-
-    // Initialized in init().
-    DragLayer mDragLayer;
-
-    /**
-     * @param draggedItem The info of the item that was long clicked, which we will use to find
-     *                    the equivalent match on Hotseat to drag internally.
-     */
-    public TaskbarDragListener(BaseQuickstepLauncher launcher, ItemInfo draggedItem) {
-        mLauncher = launcher;
-        mDraggedItem = draggedItem;
-        mId = UUID.randomUUID().toString();
-    }
-
-    protected void init(DragLayer dragLayer) {
-        mDragLayer = dragLayer;
-        mDragLayer.setOnDragListener(this);
-        // Temporarily disable haptics, as system will already play one when drag and drop starts.
-        mDragLayer.setHapticFeedbackEnabled(false);
-    }
-
-    private void cleanup() {
-        mDragLayer.setOnDragListener(null);
-        mLauncher.setNextWorkspaceDragOptions(null);
-        mDragLayer.setHapticFeedbackEnabled(true);
-    }
-
-    /**
-     * Returns a randomly generated id used to verify the drag event.
-     */
-    protected String getMimeType() {
-        return MIME_TYPE_PREFIX + mId;
-    }
-
-    @Override
-    public boolean onDrag(View dragLayer, DragEvent dragEvent) {
-        ClipDescription clipDescription = dragEvent.getClipDescription();
-        if (dragEvent.getAction() == DragEvent.ACTION_DRAG_STARTED) {
-            if (clipDescription == null || !clipDescription.hasMimeType(getMimeType())) {
-                // We didn't initiate this drag, ignore.
-                cleanup();
-                return false;
-            }
-            View hotseatView = mLauncher.getHotseat().getFirstItemMatch(
-                    (info, view) -> info == mDraggedItem);
-            if (hotseatView == null) {
-                cleanup();
-                return false;
-            }
-            DragOptions dragOptions = new DragOptions();
-            dragOptions.simulatedDndStartPoint = new Point((int) dragEvent.getX(),
-                    (int) dragEvent.getY());
-            mLauncher.setNextWorkspaceDragOptions(dragOptions);
-            hotseatView.performLongClick();
-        } else if (dragEvent.getAction() == DragEvent.ACTION_DRAG_ENDED) {
-            cleanup();
-        }
-        return mLauncher.getDragController().onDragEvent(dragEvent);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
index b4b5d8b..9fc7d99 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java
@@ -16,14 +16,12 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherState.TASKBAR;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_TASKBAR_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_TASKBAR;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -55,8 +53,10 @@
         }
 
         AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
+        AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
         boolean isTaskbarVisible = (state.getVisibleElements(mLauncher) & TASKBAR) != 0;
         alphaTarget.updateValue(isTaskbarVisible ? 1f : 0f);
+        scaleTarget.updateValue(state.getTaskbarScale(mLauncher));
     }
 
     @Override
@@ -65,13 +65,12 @@
         if (mTaskbarCallbacks == null) {
             return;
         }
-        if (config.hasAnimationFlag(SKIP_TASKBAR)) {
-            return;
-        }
 
         AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget();
+        AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget();
         boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0;
-        animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f,
-                config.getInterpolator(ANIM_TASKBAR_FADE, Interpolators.LINEAR));
+        animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR);
+        animation.setFloat(scaleTarget, AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher),
+                LINEAR);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 1d762e9..21a2d51 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -15,11 +15,14 @@
  */
 package com.android.launcher3.taskbar;
 
+import android.animation.Animator;
+import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -36,6 +39,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.folder.FolderIcon;
@@ -48,20 +52,23 @@
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
  */
-public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent {
+public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable {
 
     private final ColorDrawable mBackgroundDrawable;
-    private final int mItemMarginLeftRight;
+    private final int mDividerWidth;
+    private final int mDividerHeight;
     private final int mIconTouchSize;
     private final boolean mIsRtl;
     private final int mTouchSlop;
     private final RectF mTempDelegateBounds = new RectF();
     private final RectF mDelegateSlopBounds = new RectF();
     private final int[] mTempOutLocation = new int[2];
-    private final Matrix mTempMatrix = new Matrix();
 
     // Initialized in TaskbarController constructor.
     private TaskbarController.TaskbarViewCallbacks mControllerCallbacks;
+    // Scale on elements that aren't icons.
+    private float mNonIconScale;
+    private int mItemMarginLeftRight;
 
     // Initialized in init().
     private LayoutTransition mLayoutTransition;
@@ -78,7 +85,6 @@
     private boolean mIsDraggingItem;
     // Only non-null when the corresponding Folder is open.
     private @Nullable FolderIcon mLeaveBehindFolderIcon;
-    private boolean mIsHotseatHidden;
 
     public TaskbarView(@NonNull Context context) {
         this(context, null);
@@ -99,7 +105,8 @@
 
         Resources resources = getResources();
         mBackgroundDrawable = (ColorDrawable) getBackground();
-        mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+        mDividerWidth = resources.getDimensionPixelSize(R.dimen.taskbar_divider_thickness);
+        mDividerHeight = resources.getDimensionPixelSize(R.dimen.taskbar_divider_height);
         mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size);
         mIsRtl = Utilities.isRtl(resources);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
@@ -107,12 +114,12 @@
 
     protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) {
         mControllerCallbacks = taskbarViewCallbacks;
+        mNonIconScale = mControllerCallbacks.getNonIconScale(this);
+        mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
+        mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale);
     }
 
     protected void init(int numHotseatIcons, int numRecentIcons) {
-        mLayoutTransition = new LayoutTransition();
-        setLayoutTransitionsEnabled(true);
-
         mHotseatStartIndex = 0;
         mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1;
         updateHotseatItems(new ItemInfo[numHotseatIcons]);
@@ -123,14 +130,50 @@
         mRecentsStartIndex = dividerIndex + 1;
         mRecentsEndIndex = mRecentsStartIndex + numRecentIcons - 1;
         updateRecentTasks(new Task[numRecentIcons]);
+
+        mLayoutTransition = new LayoutTransition();
+        addUpdateListenerForAllLayoutTransitions(() -> {
+            if (getLayoutTransition() == mLayoutTransition) {
+                mControllerCallbacks.onItemPositionsChanged(this);
+            }
+        });
+        setLayoutTransition(mLayoutTransition);
     }
 
-    private void setLayoutTransitionsEnabled(boolean enabled) {
-        setLayoutTransition(enabled ? mLayoutTransition : null);
+    private void addUpdateListenerForAllLayoutTransitions(Runnable onUpdate) {
+        addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_APPEARING, onUpdate);
+        addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_DISAPPEARING, onUpdate);
+        addUpdateListenerForLayoutTransition(LayoutTransition.CHANGING, onUpdate);
+        addUpdateListenerForLayoutTransition(LayoutTransition.APPEARING, onUpdate);
+        addUpdateListenerForLayoutTransition(LayoutTransition.DISAPPEARING, onUpdate);
+    }
+
+    private void addUpdateListenerForLayoutTransition(int transitionType, Runnable onUpdate) {
+        Animator anim = mLayoutTransition.getAnimator(transitionType);
+        if (anim instanceof ValueAnimator) {
+            ((ValueAnimator) anim).addUpdateListener(valueAnimator -> onUpdate.run());
+        } else {
+            AnimatorSet animSet = new AnimatorSet();
+            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
+            updateAnim.addUpdateListener(valueAnimator -> onUpdate.run());
+            animSet.playTogether(anim, updateAnim);
+            mLayoutTransition.setAnimator(transitionType, animSet);
+        }
     }
 
     protected void cleanup() {
+        endAllLayoutTransitionAnimators();
+        setLayoutTransition(null);
         removeAllViews();
+        mHotseatRecentsDivider = null;
+    }
+
+    private void endAllLayoutTransitionAnimators() {
+        mLayoutTransition.getAnimator(LayoutTransition.CHANGE_APPEARING).end();
+        mLayoutTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING).end();
+        mLayoutTransition.getAnimator(LayoutTransition.CHANGING).end();
+        mLayoutTransition.getAnimator(LayoutTransition.APPEARING).end();
+        mLayoutTransition.getAnimator(LayoutTransition.DISAPPEARING).end();
     }
 
     /**
@@ -170,12 +213,11 @@
             if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId
                     || needsReinflate) {
                 removeView(hotseatView);
-                TaskbarActivityContext activityContext =
-                        ActivityContext.lookupContext(getContext());
+                ActivityContext activityContext = ActivityContext.lookupContext(getContext());
                 if (isFolder) {
                     FolderInfo folderInfo = (FolderInfo) hotseatItemInfo;
                     FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId,
-                            activityContext, this, folderInfo);
+                            ActivityContext.lookupContext(getContext()), this, folderInfo);
                     folderIcon.setTextVisible(false);
                     hotseatView = folderIcon;
                 } else {
@@ -216,22 +258,12 @@
         }
     }
 
-    /**
-     * Hides or shows the hotseat items immediately (without layout transitions).
-     */
-    protected void setHotseatViewsHidden(boolean hidden) {
-        mIsHotseatHidden = hidden;
-        setLayoutTransitionsEnabled(false);
-        updateHotseatItemsVisibility();
-        setLayoutTransitionsEnabled(true);
-    }
-
     private void updateHotseatItemVisibility(View hotseatView) {
         if (hotseatView.getTag() != null) {
-            hotseatView.setVisibility(mIsHotseatHidden ? INVISIBLE : VISIBLE);
+            hotseatView.setVisibility(VISIBLE);
         } else {
             int oldVisibility = hotseatView.getVisibility();
-            int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility();
+            int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility(this);
             hotseatView.setVisibility(newVisibility);
             if (oldVisibility == GONE && newVisibility != GONE) {
                 // By default, the layout transition only runs when going to VISIBLE,
@@ -243,7 +275,11 @@
 
     private View addDivider(int dividerIndex) {
         View divider = inflate(R.layout.taskbar_divider);
-        addView(divider, dividerIndex);
+        LayoutParams lp = new LayoutParams(mDividerWidth, mDividerHeight);
+        lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0);
+        divider.setScaleX(mNonIconScale);
+        divider.setScaleY(mNonIconScale);
+        addView(divider, dividerIndex, lp);
         return divider;
     }
 
@@ -437,9 +473,9 @@
     }
 
     /**
-     * @return The bounding box of where the hotseat elements will be when we reach the given scale.
+     * @return The bounding box of where the hotseat elements are relative to this TaskbarView.
      */
-    protected RectF getHotseatBoundsAtScale(float taskbarViewScale) {
+    protected RectF getHotseatBounds() {
         View firstHotseatView = null, lastHotseatView = null;
         for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) {
             View child = getChildAt(i);
@@ -455,14 +491,11 @@
         }
         View leftmostHotseatView = !mIsRtl ? firstHotseatView : lastHotseatView;
         View rightmostHotseatView = !mIsRtl ? lastHotseatView : firstHotseatView;
-        RectF hotseatBounds = new RectF(
+        return new RectF(
                 leftmostHotseatView.getLeft() - mItemMarginLeftRight,
                 leftmostHotseatView.getTop(),
                 rightmostHotseatView.getRight() + mItemMarginLeftRight,
                 rightmostHotseatView.getBottom());
-        mTempMatrix.setScale(taskbarViewScale, taskbarViewScale, getPivotX(), getPivotY());
-        mTempMatrix.mapRect(hotseatBounds);
-        return hotseatBounds;
     }
 
     // FolderIconParent implemented methods.
@@ -493,7 +526,12 @@
     }
 
     private View inflate(@LayoutRes int layoutResId) {
-        TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext());
-        return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false);
+        return ActivityContext.lookupContext(getContext()).getLayoutInflater()
+                .inflate(layoutResId, this, false);
+    }
+
+    @Override
+    public void setInsets(Rect insets) {
+        // Ignore, we just implement Insettable to draw behind system insets.
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 750f673..e1456b1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -25,6 +25,7 @@
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
 import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
+import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
 import android.annotation.TargetApi;
 import android.os.Build;
@@ -73,7 +74,7 @@
 
         if (toState.overviewUi) {
             // While animating into recents, update the visible task data as needed
-            builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
+            builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
             mRecentsView.updateEmptyMessage();
         } else {
             builder.addListener(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
index 36c0e34..1417995 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
@@ -102,9 +102,11 @@
     private void notifyChange() {
         // Create a new array to avoid concurrent modification when the activity destroys itself.
         mTempListeners = mListeners.toArray(mTempListeners);
-        for (OnChangeListener listener : mTempListeners) {
+        for (int i = mTempListeners.length - 1; i >= 0; --i) {
+            final OnChangeListener listener = mTempListeners[i];
             if (listener != null) {
                 listener.onExtractedColorsChanged(this);
+                mTempListeners[i] = null;
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index fb58bf6..aa770d2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -82,20 +82,6 @@
     }
 
     @Override
-    public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
-        if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
-            // Translate hotseat offscreen if we show it in overview.
-            RecentsView recentsView = launcher.getOverviewPanel();
-            ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
-            scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
-                    launcher.getDeviceProfile(),
-                    recentsView.getPagedOrientationHandler());
-            return scaleAndTranslation;
-        }
-        return super.getHotseatScaleAndTranslation(launcher);
-    }
-
-    @Override
     protected float getDepthUnchecked(Context context) {
         return 1f;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 5a28cfd..43e70a3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -78,37 +78,13 @@
     }
 
     @Override
-    public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
-        if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
-            DeviceProfile dp = launcher.getDeviceProfile();
-            if (dp.allAppsIconSizePx >= dp.iconSizePx) {
-                return new ScaleAndTranslation(1, 0, 0);
-            } else {
-                float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
-                // Distance between the screen center (which is the pivotY for hotseat) and the
-                // bottom of the hotseat (which we want to preserve)
-                float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
-                // On scaling, the bottom edge is moved closer to the pivotY. We move the
-                // hotseat back down so that the bottom edge's position is preserved.
-                float translationY = distanceFromBottom * (1 - scale);
-                return new ScaleAndTranslation(scale, 0, translationY);
-            }
-        }
-        return getWorkspaceScaleAndTranslation(launcher);
-    }
-
-    @Override
     public float[] getOverviewScaleAndOffset(Launcher launcher) {
         return new float[] {NO_SCALE, NO_OFFSET};
     }
 
     @Override
-    public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
-        if (this == OVERVIEW) {
-            // Treat the QSB as part of the hotseat so they move together.
-            return getHotseatScaleAndTranslation(launcher);
-        }
-        return super.getQsbScaleAndTranslation(launcher);
+    public float getTaskbarScale(Launcher launcher) {
+        return 1f;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 473fe2d..de7be5d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -18,7 +18,6 @@
 import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.LauncherState.HINT_STATE;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
@@ -33,12 +32,10 @@
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_TASKBAR_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
@@ -82,7 +79,6 @@
         if (toState == NORMAL && fromState == OVERVIEW) {
             config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
-            config.setInterpolator(ANIM_TASKBAR_FADE, ACCEL);
             config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
@@ -141,7 +137,6 @@
             config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_2);
-            config.setInterpolator(ANIM_TASKBAR_FADE, OVERSHOOT_1_2);
         } else if (fromState == HINT_STATE && toState == NORMAL) {
             config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
             if (mHintToNormalDuration == -1) {
@@ -150,17 +145,6 @@
                 mHintToNormalDuration = (int) va.getDuration();
             }
             config.duration = Math.max(config.duration, mHintToNormalDuration);
-        } else if (mActivity.getTaskbarController() != null)  {
-            boolean wasHotseatVisible = fromState.areElementsVisible(mActivity, HOTSEAT_ICONS);
-            boolean isHotseatVisible = toState.areElementsVisible(mActivity, HOTSEAT_ICONS);
-            if (wasHotseatVisible || isHotseatVisible) {
-                config.setInterpolator(ANIM_TASKBAR_FADE, INSTANT);
-                config.setInterpolator(ANIM_HOTSEAT_FADE, INSTANT);
-
-                if (isHotseatVisible) {
-                    mActivity.getTaskbarController().alignRealHotseatWithTaskbar();
-                }
-            }
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index b492825..1a5f9c2 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -22,7 +22,6 @@
 
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
-import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -97,11 +96,12 @@
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.InputConsumerProxy;
+import com.android.quickstep.util.InputProxyHandlerFactory;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.ProtoTracer;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TransformParams;
@@ -113,12 +113,10 @@
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.TaskInfoCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.function.Consumer;
 
 /**
@@ -200,7 +198,7 @@
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
 
     public static final long MAX_SWIPE_DURATION = 350;
-    public static final long MIN_OVERSHOOT_DURATION = 120;
+    public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS;
 
     public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
     private static final float SWIPE_DURATION_MULTIPLIER =
@@ -255,7 +253,10 @@
         mActivityInterface = gestureState.getActivityInterface();
         mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
         mInputConsumerProxy =
-                new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler);
+                new InputConsumerProxy(inputConsumer, () -> {
+                    endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
+                    endLauncherTransitionController();
+                }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
         mTaskAnimationManager = taskAnimationManager;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
@@ -783,19 +784,6 @@
         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
-    /**
-     * Called to create a input proxy for the running task
-     */
-    @UiThread
-    protected InputConsumer createNewInputProxyHandler() {
-        endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
-        endLauncherTransitionController();
-
-        StatefulActivity activity = mActivityInterface.getCreatedActivity();
-        return activity == null ? InputConsumer.NO_OP
-                : new OverviewInputConsumer(mGestureState, activity, null, true);
-    }
-
     private void endRunningWindowAnim(boolean cancel) {
         if (mRunningWindowAnim != null) {
             if (cancel) {
@@ -923,6 +911,9 @@
         float currentShift = mCurrentShift.value;
         final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
                 isFling, isCancel);
+        // Set the state, but don't notify until the animation completes
+        mGestureState.setEndTarget(endTarget, false /* isAtomic */);
+
         float endShift = endTarget.isLauncher ? 1 : 0;
         final float startShift;
         if (!isFling) {
@@ -945,7 +936,7 @@
         }
         Interpolator interpolator;
         S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
-        if (state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) {
+        if (state.displayOverviewTasksAsGrid(mDp)) {
             interpolator = ACCEL_DEACCEL;
         } else if (endTarget == RECENTS) {
             interpolator = OVERSHOOT_1_2;
@@ -957,7 +948,7 @@
             mInputConsumerProxy.enable();
         }
         if (endTarget == HOME) {
-            duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
+            duration = HOME_DURATION;
         } else if (endTarget == RECENTS) {
             if (mRecentsView != null) {
                 int nearestPage = mRecentsView.getDestinationPage();
@@ -1055,8 +1046,6 @@
     @UiThread
     private void animateToProgressInternal(float start, float end, long duration,
             Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
-        // Set the state, but don't notify until the animation completes
-        mGestureState.setEndTarget(target, false /* isAtomic */);
         maybeUpdateRecentsAttachedState();
 
         // If we are transitioning to launcher, then listen for the activity to be restarted while
@@ -1147,10 +1136,8 @@
             });
             animatorSet.play(windowAnim);
             S state = mActivityInterface.stateFromGestureEndTarget(mGestureState.getEndTarget());
-            if (mRecentsView != null && state.displayOverviewTasksAsGrid(
-                    mActivity.getDeviceProfile())) {
+            if (mRecentsView != null && state.displayOverviewTasksAsGrid(mDp)) {
                 animatorSet.play(ObjectAnimator.ofFloat(mRecentsView, RECENTS_GRID_PROGRESS, 1));
-                animatorSet.play(mTaskViewSimulator.gridProgress.animateToValue(0, 1));
             }
             animatorSet.setDuration(duration).setInterpolator(interpolator);
             animatorSet.start();
@@ -1339,6 +1326,7 @@
             mInputConsumerProxy.destroy();
             mTaskAnimationManager.setLiveTileCleanUpHandler(null);
         }
+        mInputConsumerProxy.unregisterCallback();
         endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
@@ -1503,36 +1491,13 @@
 
     protected abstract void finishRecentsControllerToHome(Runnable callback);
 
-    private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
-        @Override
-        public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
-                boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
-            if (mRecentsView.getRunningTaskIndex() != -1
-                    && mRecentsView.getRunningTaskId() == task.taskId
-                    && mRecentsAnimationTargets.hasTask(task.taskId)) {
-                launchOtherTaskInLiveTileMode(task.taskId, mRecentsAnimationTargets.apps);
-            }
-            ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
-                    mLiveTileRestartListener);
-        }
-    };
-
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
         mActivityInterface.onSwipeUpToRecentsComplete();
         mRecentsView.onSwipeUpAnimationSuccess();
         if (LIVE_TILE.get()) {
-            mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
-                    appearedTaskTarget -> {
-                        RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
-                                mRecentsAnimationTargets.apps,
-                                mRecentsAnimationTargets.apps.length + 1);
-                        apps[apps.length - 1] = appearedTaskTarget;
-                        launchOtherTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
-                    });
             mTaskAnimationManager.setLiveTileCleanUpHandler(mInputConsumerProxy::destroy);
-            ActivityManagerWrapper.getInstance().registerTaskStackListener(
-                    mLiveTileRestartListener);
+            mTaskAnimationManager.enableLiveTileRestartListener();
         }
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
@@ -1540,65 +1505,6 @@
         reset();
     }
 
-    private void launchOtherTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps) {
-        AnimatorSet anim = new AnimatorSet();
-        TaskView taskView = mRecentsView.getTaskView(taskId);
-        if (taskView == null || !mRecentsView.isTaskViewVisible(taskView)) {
-            // TODO: Refine this animation.
-            SurfaceTransactionApplier surfaceApplier =
-                    new SurfaceTransactionApplier(mActivity.getDragLayer());
-            ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
-            appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
-            appAnimator.setInterpolator(ACCEL_DEACCEL);
-            appAnimator.addUpdateListener(new MultiValueUpdateListener() {
-                @Override
-                public void onUpdate(float percent) {
-                    SurfaceParams.Builder builder = new SurfaceParams.Builder(
-                            apps[apps.length - 1].leash);
-                    Matrix matrix = new Matrix();
-                    matrix.postScale(percent, percent);
-                    matrix.postTranslate(mDp.widthPx * (1 - percent) / 2,
-                            mDp.heightPx * (1 - percent) / 2);
-                    builder.withAlpha(percent).withMatrix(matrix);
-                    surfaceApplier.scheduleApply(builder.build());
-                }
-            });
-            anim.play(appAnimator);
-        } else {
-            TaskViewUtils.composeRecentsLaunchAnimator(
-                    anim, taskView, apps,
-                    mRecentsAnimationTargets.wallpapers, true /* launcherClosing */,
-                    mActivity.getStateManager(), mRecentsView,
-                    mActivityInterface.getDepthController());
-        }
-        anim.addListener(new AnimatorListenerAdapter(){
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                cleanUp(false);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animator) {
-                cleanUp(true);
-            }
-
-            private void cleanUp(boolean canceled) {
-                if (mRecentsAnimationController != null) {
-                    mRecentsAnimationController.finish(false /* toRecents */,
-                            null /* onFinishComplete */);
-                    if (canceled) {
-                        mRecentsAnimationController = null;
-                    } else {
-                        mActivityInterface.onLaunchTaskSuccess();
-                    }
-                    ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
-                }
-            }
-        });
-        anim.start();
-    }
-
     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
         return app.isNotInRecents
                 || app.activityType == ACTIVITY_TYPE_HOME;
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 5942b3a..0415009 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -31,6 +31,7 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Build;
 import android.view.Gravity;
@@ -43,6 +44,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -195,41 +197,53 @@
     }
 
     /**
-     * Calculates the taskView size for the provided device configuration
+     * Calculates the taskView size for the provided device configuration.
      */
     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
             PagedOrientationHandler orientedState) {
         Resources res = context.getResources();
+        if (dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
+            Rect gridRect = new Rect();
+            calculateGridSize(context, dp, gridRect);
 
-        int taskMargin = res.getDimensionPixelSize(R.dimen.overview_task_margin);
-        int taskIconAndMargin = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size)
-                + res.getDimensionPixelSize(R.dimen.task_icon_top_margin);
-        int proactiveRowAndMargin = res.getDimensionPixelSize(R.dimen.overview_proactive_row_height)
-                + res.getDimensionPixelSize(R.dimen.overview_proactive_row_bottom_margin);
+            int rowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
+            float rowHeight = (gridRect.height() - rowSpacing) / 2f;
 
-        calculateTaskSizeInternal(context, dp,
-                taskIconAndMargin + taskMargin,
-                proactiveRowAndMargin + getOverviewActionsHeight(context) + taskMargin,
-                res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
-                outRect);
+            PointF taskDimension = getTaskDimension(context, dp);
+            float scale = (rowHeight - dp.overviewTaskThumbnailTopMarginPx) / Math.max(
+                    taskDimension.x, taskDimension.y);
+            int outWidth = Math.round(scale * taskDimension.x);
+            int outHeight = Math.round(scale * taskDimension.y);
+
+            int gravity = Gravity.TOP;
+            gravity |= orientedState.getRecentsRtlSetting(res) ? Gravity.RIGHT : Gravity.LEFT;
+            gridRect.inset(0, dp.overviewTaskThumbnailTopMarginPx, 0, 0);
+            Gravity.apply(gravity, outWidth, outHeight, gridRect, outRect);
+        } else {
+            int taskMargin = dp.overviewTaskMarginPx;
+            int proactiveRowAndMargin;
+            if (dp.isVerticalBarLayout()) {
+                // In Vertical Bar Layout the proactive row doesn't have its own space, it's inside
+                // the actions row.
+                proactiveRowAndMargin = 0;
+            } else {
+                proactiveRowAndMargin = res.getDimensionPixelSize(
+                        R.dimen.overview_proactive_row_height)
+                        + res.getDimensionPixelSize(R.dimen.overview_proactive_row_bottom_margin);
+            }
+            calculateTaskSizeInternal(context, dp,
+                    dp.overviewTaskThumbnailTopMarginPx,
+                    proactiveRowAndMargin + getOverviewActionsHeight(context) + taskMargin,
+                    res.getDimensionPixelSize(R.dimen.overview_minimum_next_prev_size) + taskMargin,
+                    outRect);
+        }
     }
 
     private void calculateTaskSizeInternal(Context context, DeviceProfile dp,
             int claimedSpaceAbove, int claimedSpaceBelow, int minimumHorizontalPadding,
             Rect outRect) {
-        float taskWidth, taskHeight;
+        PointF taskDimension = getTaskDimension(context, dp);
         Rect insets = dp.getInsets();
-        if (dp.isMultiWindowMode) {
-            WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
-            taskWidth = bounds.availableSize.x;
-            taskHeight = bounds.availableSize.y;
-        } else if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
-            taskWidth = dp.availableWidthPx;
-            taskHeight = dp.availableHeightPx;
-        } else {
-            taskWidth = dp.widthPx;
-            taskHeight = dp.heightPx;
-        }
 
         Rect potentialTaskRect = new Rect(0, 0, dp.widthPx, dp.heightPx);
         potentialTaskRect.inset(insets.left, insets.top, insets.right, insets.bottom);
@@ -240,14 +254,30 @@
                 claimedSpaceBelow);
 
         float scale = Math.min(
-                potentialTaskRect.width() / taskWidth,
-                potentialTaskRect.height() / taskHeight);
-        int outWidth = Math.round(scale * taskWidth);
-        int outHeight = Math.round(scale * taskHeight);
+                potentialTaskRect.width() / taskDimension.x,
+                potentialTaskRect.height() / taskDimension.y);
+        int outWidth = Math.round(scale * taskDimension.x);
+        int outHeight = Math.round(scale * taskDimension.y);
 
         Gravity.apply(Gravity.CENTER, outWidth, outHeight, potentialTaskRect, outRect);
     }
 
+    private PointF getTaskDimension(Context context, DeviceProfile dp) {
+        PointF dimension = new PointF();
+        if (dp.isMultiWindowMode) {
+            WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context);
+            dimension.x = bounds.availableSize.x;
+            dimension.y = bounds.availableSize.y;
+        } else if (TaskView.CLIP_STATUS_AND_NAV_BARS) {
+            dimension.x = dp.availableWidthPx;
+            dimension.y = dp.availableHeightPx;
+        } else {
+            dimension.x = dp.widthPx;
+            dimension.y = dp.heightPx;
+        }
+        return dimension;
+    }
+
     /**
      * Calculates the overview grid size for the provided device configuration.
      */
@@ -267,13 +297,11 @@
      * Calculates the modal taskView size for the provided device configuration
      */
     public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
-        Resources res = context.getResources();
         calculateTaskSizeInternal(
                 context, dp,
-                res.getDimensionPixelSize(R.dimen.overview_task_margin),
-                getOverviewActionsHeight(context)
-                        + res.getDimensionPixelSize(R.dimen.overview_task_margin),
-                res.getDimensionPixelSize(R.dimen.overview_task_margin),
+                dp.overviewTaskMarginPx,
+                getOverviewActionsHeight(context) + dp.overviewTaskMarginPx,
+                dp.overviewTaskMarginPx,
                 outRect);
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index da0a664..718c5ba 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -41,13 +41,4 @@
     public boolean hasTargets() {
         return unfilteredApps.length != 0;
     }
-
-    public boolean hasTask(int taskId) {
-        for (RemoteAnimationTargetCompat target : unfilteredApps) {
-            if (target.taskId == taskId) {
-                return true;
-            }
-        }
-        return false;
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 9a454f2..b6dad2d 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -17,10 +17,11 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
@@ -31,13 +32,13 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.RemoteTransitionCompat;
-
-import java.util.function.Consumer;
+import com.android.systemui.shared.system.TaskStackChangeListener;
 
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
     public static final boolean ENABLE_SHELL_TRANSITIONS =
@@ -49,10 +50,24 @@
     // Temporary until we can hook into gesture state events
     private GestureState mLastGestureState;
     private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
-    private Consumer<RemoteAnimationTargetCompat> mLaunchOtherTaskHandler;
     private Runnable mLiveTileCleanUpHandler;
     private Context mCtx;
 
+    private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
+        @Override
+        public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+                boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+            BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+            if (LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+                    && activityInterface.getCreatedActivity() != null) {
+                RecentsView recentsView = activityInterface.getCreatedActivity().getOverviewPanel();
+                recentsView.launchSideTaskInLiveTileModeForRestartedApp(task.taskId);
+                ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
+                        mLiveTileRestartListener);
+            }
+        }
+    };
+
     TaskAnimationManager(Context ctx) {
         mCtx = ctx;
     }
@@ -114,9 +129,14 @@
 
             @Override
             public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
-                if (mLaunchOtherTaskHandler != null
-                        && mLastGestureState.getEndTarget() == RECENTS) {
-                    mLaunchOtherTaskHandler.accept(appearedTaskTarget);
+                BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
+                if (LIVE_TILE.get() && activityInterface.isInLiveTileMode()
+                        && activityInterface.getCreatedActivity() != null) {
+                    RecentsView recentsView =
+                            activityInterface.getCreatedActivity().getOverviewPanel();
+                    RemoteAnimationTargetCompat[] apps = new RemoteAnimationTargetCompat[1];
+                    apps[0] = appearedTaskTarget;
+                    recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
                     return;
                 }
                 if (mController != null) {
@@ -161,17 +181,12 @@
         return mCallbacks;
     }
 
-    /**
-     * The passed-in handler is used to render side task launch animation in recents in live tile
-     * mode.
-     */
-    public void setLaunchOtherTaskInLiveTileModeHandler(
-            Consumer<RemoteAnimationTargetCompat> handler) {
-        mLaunchOtherTaskHandler = handler;
+    public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
+        mLiveTileCleanUpHandler = cleanUpHandler;
     }
 
-    public void setLiveTileCleanUpHandler(Runnable runnable) {
-        mLiveTileCleanUpHandler = runnable;
+    public void enableLiveTileRestartListener() {
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mLiveTileRestartListener);
     }
 
     /**
@@ -215,6 +230,7 @@
             mLiveTileCleanUpHandler.run();
             mLiveTileCleanUpHandler = null;
         }
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mLiveTileRestartListener);
 
         // Release all the target leashes
         if (mTargets != null) {
@@ -231,7 +247,6 @@
         mTargets = null;
         mLastGestureState = null;
         mLastAppearedTaskTarget = null;
-        mLaunchOtherTaskHandler = null;
     }
 
     public void dump() {
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index dd0ed8f..08503cf 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
 import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
 
 import android.app.ActivityManager.TaskDescription;
@@ -34,7 +33,6 @@
 
 import androidx.annotation.WorkerThread;
 
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapInfo;
@@ -137,11 +135,12 @@
         // TODO: Load icon resource (b/143363444)
         Bitmap icon = TaskDescriptionCompat.getIcon(desc, key.userId);
         if (icon != null) {
-            entry.icon = new FastBitmapDrawable(getBitmapInfo(
+            /* isInstantApp */
+            entry.icon = getBitmapInfo(
                     new BitmapDrawable(mContext.getResources(), icon),
                     key.userId,
                     desc.getPrimaryColor(),
-                    false /* isInstantApp */));
+                    false /* isInstantApp */).newIcon(mContext);
         } else {
             activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
                     key.getComponent(), key.userId);
@@ -151,7 +150,7 @@
                         key.userId,
                         desc.getPrimaryColor(),
                         activityInfo.applicationInfo.isInstantApp());
-                entry.icon = newIcon(mContext, bitmapInfo);
+                entry.icon = bitmapInfo.newIcon(mContext);
             } else {
                 entry.icon = getDefaultIcon(key.userId);
             }
@@ -199,7 +198,7 @@
                 }
                 mDefaultIcons.put(userId, info);
             }
-            return new FastBitmapDrawable(info);
+            return info.newIcon(mContext);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index cd13200..1ad5f2c 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -176,7 +176,7 @@
 
             if (thumbnail != null) {
                 getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
-                boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+                boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot();
                 getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
             }
         }
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 7a428ce..79db842 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
@@ -200,8 +201,7 @@
             tsv.setPreview(targets.apps[targets.apps.length - 1]);
             tsv.fullScreenProgress.value = 0;
             tsv.recentsViewScale.value = 1;
-            tsv.gridProgress.value = gridProgress;
-            tsv.gridTranslationSecondary.value = gridTranslationSecondary;
+            tsv.taskSecondaryTranslation.value = gridTranslationSecondary;
             tsv.setScroll(startScroll);
 
             // Fade in the task during the initial 20% of the animation
@@ -214,7 +214,8 @@
                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
             out.setFloat(tsv.recentsViewScale,
                     AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
-            out.setFloat(tsv.gridProgress, AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
+            out.setFloat(tsv.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
+                    TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL);
             out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
 
             TaskViewSimulator finalTsv = tsv;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 54f6ce6..82bfa9b 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -29,6 +29,7 @@
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
+import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
@@ -70,7 +71,7 @@
             return;
         }
         // While animating into recents, update the visible task data as needed
-        setter.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
+        setter.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
         mRecentsView.updateEmptyMessage();
 
         setProperties(toState, config, setter);
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 161e015..a1b7e01 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -36,9 +36,8 @@
     Integer getTitleStringId() {
         switch (mTutorialType) {
             case RIGHT_EDGE_BACK_NAVIGATION:
-                return R.string.back_gesture_tutorial_playground_title_swipe_inward_right_edge;
             case LEFT_EDGE_BACK_NAVIGATION:
-                return R.string.back_gesture_tutorial_playground_title_swipe_inward_left_edge;
+                return R.string.back_gesture_intro_title;
             case BACK_NAVIGATION_COMPLETE:
                 return R.string.gesture_tutorial_confirm_title;
         }
@@ -49,9 +48,8 @@
     Integer getSubtitleStringId() {
         switch (mTutorialType) {
             case RIGHT_EDGE_BACK_NAVIGATION:
-                return R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge;
             case LEFT_EDGE_BACK_NAVIGATION:
-                return R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge;
+                return R.string.back_gesture_intro_subtitle;
             case BACK_NAVIGATION_COMPLETE:
                 return R.string.back_gesture_tutorial_confirm_subtitle;
         }
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 95352d1..fbf3a0a 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -38,7 +38,7 @@
     Integer getTitleStringId() {
         switch (mTutorialType) {
             case HOME_NAVIGATION:
-                return R.string.home_gesture_tutorial_playground_title;
+                return R.string.home_gesture_intro_title;
             case HOME_NAVIGATION_COMPLETE:
                 return R.string.gesture_tutorial_confirm_title;
         }
@@ -48,7 +48,7 @@
     @Override
     Integer getSubtitleStringId() {
         if (mTutorialType == TutorialType.HOME_NAVIGATION) {
-            return R.string.home_gesture_tutorial_playground_subtitle;
+            return R.string.home_gesture_intro_subtitle;
         }
         return null;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index 45cbd0b..31f26d1 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -39,7 +39,7 @@
     Integer getTitleStringId() {
         switch (mTutorialType) {
             case OVERVIEW_NAVIGATION:
-                return R.string.overview_gesture_tutorial_playground_title;
+                return R.string.overview_gesture_intro_title;
             case OVERVIEW_NAVIGATION_COMPLETE:
                 return R.string.gesture_tutorial_confirm_title;
         }
@@ -49,7 +49,7 @@
     @Override
     Integer getSubtitleStringId() {
         if (mTutorialType == TutorialType.OVERVIEW_NAVIGATION) {
-            return R.string.overview_gesture_tutorial_playground_subtitle;
+            return R.string.overview_gesture_intro_subtitle;
         }
         return null;
     }
diff --git a/quickstep/src/com/android/quickstep/util/AssistContentRequester.java b/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
new file mode 100644
index 0000000..3730284
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AssistContentRequester.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 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.util;
+
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.IAssistDataReceiver;
+import android.app.assist.AssistContent;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.launcher3.util.Executors;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Can be used to request the AssistContent from a provided task id, useful for getting the web uri
+ * if provided from the task.
+ */
+public class AssistContentRequester {
+    private static final String TAG = "AssistContentRequester";
+    private static final String ASSIST_KEY_CONTENT = "content";
+
+    /** For receiving content, called on the main thread. */
+    public interface Callback {
+        /**
+         * Called when the {@link android.app.assist.AssistContent} of the requested task is
+         * available.
+         **/
+        void onAssistContentAvailable(AssistContent assistContent);
+    }
+
+    private final IActivityTaskManager mActivityTaskManager;
+    private final String mPackageName;
+    private final Executor mCallbackExecutor;
+
+    public AssistContentRequester(Context context) {
+        mActivityTaskManager = ActivityTaskManager.getService();
+        mPackageName = context.getApplicationContext().getPackageName();
+        mCallbackExecutor = Executors.MAIN_EXECUTOR;
+    }
+
+    /**
+     * Request the {@link AssistContent} from the task with the provided id.
+     *
+     * @param taskId to query for the content.
+     * @param callback to call when the content is available, called on the main thread.
+     */
+    public void requestAssistContent(int taskId, Callback callback) {
+        try {
+            mActivityTaskManager.requestAssistDataForTask(
+                    new AssistDataReceiver(callback, mCallbackExecutor), taskId, mPackageName);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Requesting assist content failed for task: " + taskId, e);
+        }
+    }
+
+    private static final class AssistDataReceiver extends IAssistDataReceiver.Stub {
+
+        private final Executor mExecutor;
+        private final Callback mCallback;
+
+        AssistDataReceiver(Callback callback, Executor callbackExecutor) {
+            mCallback = callback;
+            mExecutor = callbackExecutor;
+        }
+
+        @Override
+        public void onHandleAssistData(Bundle data) {
+            if (data == null) {
+                return;
+            }
+
+            final AssistContent content = data.getParcelable(ASSIST_KEY_CONTENT);
+            if (content == null) {
+                Log.e(TAG, "Received AssistData, but no AssistContent found");
+                return;
+            }
+
+            mExecutor.execute(() -> mCallback.onAssistContentAvailable(content));
+        }
+
+        @Override
+        public void onHandleAssistScreenshot(Bitmap screenshot) {}
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
index 3e87f48..2e5b33a 100644
--- a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -38,7 +38,8 @@
     private static final String TAG = "InputConsumerProxy";
 
     private final InputConsumerController mInputConsumerController;
-    private final Supplier<InputConsumer> mConsumerSupplier;
+    private Runnable mCallback;
+    private Supplier<InputConsumer> mConsumerSupplier;
 
     // The consumer is created lazily on demand.
     private InputConsumer mInputConsumer;
@@ -48,8 +49,9 @@
     private boolean mDestroyPending = false;
 
     public InputConsumerProxy(InputConsumerController inputConsumerController,
-            Supplier<InputConsumer> consumerSupplier) {
+            Runnable callback, Supplier<InputConsumer> consumerSupplier) {
         mInputConsumerController = inputConsumerController;
+        mCallback = callback;
         mConsumerSupplier = consumerSupplier;
     }
 
@@ -64,9 +66,7 @@
         if (ev instanceof MotionEvent) {
             onInputConsumerMotionEvent((MotionEvent) ev);
         } else if (ev instanceof KeyEvent) {
-            if (mInputConsumer == null) {
-                mInputConsumer = mConsumerSupplier.get();
-            }
+            initInputConsumerIfNeeded();
             mInputConsumer.onKeyEvent((KeyEvent) ev);
             return true;
         }
@@ -89,9 +89,7 @@
 
         if (action == ACTION_DOWN) {
             mTouchInProgress = true;
-            if (mInputConsumer == null) {
-                mInputConsumer = mConsumerSupplier.get();
-            }
+            initInputConsumerIfNeeded();
         } else if (action == ACTION_CANCEL || action == ACTION_UP) {
             // Finish any pending actions
             mTouchInProgress = false;
@@ -115,4 +113,18 @@
         mDestroyed = true;
         mInputConsumerController.setInputListener(null);
     }
+
+    public void unregisterCallback() {
+        mCallback = null;
+    }
+
+    private void initInputConsumerIfNeeded() {
+        if (mInputConsumer == null) {
+            if (mCallback != null) {
+                mCallback.run();
+            }
+            mInputConsumer = mConsumerSupplier.get();
+            mConsumerSupplier = null;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
new file mode 100644
index 0000000..8209c09
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/InputProxyHandlerFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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.util;
+
+import androidx.annotation.UiThread;
+
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.quickstep.BaseActivityInterface;
+import com.android.quickstep.GestureState;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.inputconsumers.OverviewInputConsumer;
+
+import java.util.function.Supplier;
+
+/**
+ * A factory that creates a input consumer for
+ *  {@link com.android.quickstep.util.InputConsumerProxy}.
+ */
+public class InputProxyHandlerFactory implements Supplier<InputConsumer> {
+
+    private final BaseActivityInterface mActivityInterface;
+    private final GestureState mGestureState;
+
+    @UiThread
+    public InputProxyHandlerFactory(BaseActivityInterface activityInterface,
+            GestureState gestureState) {
+        mActivityInterface = activityInterface;
+        mGestureState = gestureState;
+    }
+
+    /**
+     * Called to create a input proxy for the running task
+     */
+    @Override
+    public InputConsumer get() {
+        StatefulActivity activity = mActivityInterface.getCreatedActivity();
+        return activity == null ? InputConsumer.NO_OP
+                : new OverviewInputConsumer(mGestureState, activity, null, true);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java b/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
index 60c7add..351adf4 100644
--- a/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
+++ b/quickstep/src/com/android/quickstep/util/NavigationModeFeatureFlag.java
@@ -20,6 +20,8 @@
 
 import android.content.Context;
 
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.SysUINavigationMode;
 
 import java.util.function.Predicate;
@@ -35,6 +37,7 @@
     private final Supplier<Boolean> mBasePredicate;
     private final Predicate<SysUINavigationMode.Mode> mModePredicate;
     private boolean mSupported;
+    private OverviewComponentObserver mObserver;
 
     private NavigationModeFeatureFlag(Supplier<Boolean> basePredicate,
             Predicate<SysUINavigationMode.Mode> modePredicate) {
@@ -43,12 +46,16 @@
     }
 
     public boolean get() {
-        return mBasePredicate.get() && mSupported;
+        return mBasePredicate.get() && mSupported && mObserver.isHomeAndOverviewSame();
     }
 
     public void initialize(Context context) {
         onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context).getMode());
         SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+
+        // Temporary solution to disable live tile for the fallback launcher
+        RecentsAnimationDeviceState rads = new RecentsAnimationDeviceState(context);
+        mObserver = new OverviewComponentObserver(context, rads);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index ae644cd..f68e936 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_TASKBAR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -61,7 +60,10 @@
 public class StaggeredWorkspaceAnim {
 
     private static final int APP_CLOSE_ROW_START_DELAY_MS = 10;
+    // How long it takes to fade in each staggered row.
     private static final int ALPHA_DURATION_MS = 250;
+    // Should be used for animations running alongside this StaggeredWorkspaceAnim.
+    public static final int DURATION_MS = 250;
 
     private static final float MAX_VELOCITY_PX_PER_S = 22f;
 
@@ -83,81 +85,106 @@
 
         DeviceProfile grid = launcher.getDeviceProfile();
         Workspace workspace = launcher.getWorkspace();
-        CellLayout cellLayout = (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
-        ShortcutAndWidgetContainer currentPage = cellLayout.getShortcutsAndWidgets();
         Hotseat hotseat = launcher.getHotseat();
 
+        // Hotseat and QSB takes up two additional rows.
+        int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
+
+        // Add animation for all the visible workspace pages
+        workspace.getVisiblePages()
+                .forEach(page -> addAnimationForPage((CellLayout) page, totalRows));
+
         boolean workspaceClipChildren = workspace.getClipChildren();
         boolean workspaceClipToPadding = workspace.getClipToPadding();
-        boolean cellLayoutClipChildren = cellLayout.getClipChildren();
-        boolean cellLayoutClipToPadding = cellLayout.getClipToPadding();
         boolean hotseatClipChildren = hotseat.getClipChildren();
         boolean hotseatClipToPadding = hotseat.getClipToPadding();
 
         workspace.setClipChildren(false);
         workspace.setClipToPadding(false);
-        cellLayout.setClipChildren(false);
-        cellLayout.setClipToPadding(false);
         hotseat.setClipChildren(false);
         hotseat.setClipToPadding(false);
 
-        // Hotseat and QSB takes up two additional rows.
-        int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2);
-
-        // Set up springs on workspace items.
-        for (int i = currentPage.getChildCount() - 1; i >= 0; i--) {
-            View child = currentPage.getChildAt(i);
-            CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
-            addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows);
-        }
-
         // Set up springs for the hotseat and qsb.
-        ViewGroup hotseatChild = (ViewGroup) hotseat.getChildAt(0);
+        ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
         if (grid.isVerticalBarLayout()) {
-            for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
-                View child = hotseatChild.getChildAt(i);
+            for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+                View child = hotseatIcons.getChildAt(i);
                 CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
                 addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
             }
         } else {
-            for (int i = hotseatChild.getChildCount() - 1; i >= 0; i--) {
-                View child = hotseatChild.getChildAt(i);
-                addStaggeredAnimationForView(child, grid.inv.numRows + 1, totalRows);
+            final int hotseatRow, qsbRow, taskbarRow;
+            if (grid.isTaskbarPresent) {
+                qsbRow = grid.inv.numRows + 1;
+                hotseatRow = grid.inv.numRows + 2;
+            } else {
+                hotseatRow = grid.inv.numRows + 1;
+                qsbRow = grid.inv.numRows + 2;
+            }
+            // Taskbar and hotseat overlap.
+            taskbarRow = hotseatRow;
+
+            for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
+                View child = hotseatIcons.getChildAt(i);
+                addStaggeredAnimationForView(child, hotseatRow, totalRows);
             }
 
-            addStaggeredAnimationForView(hotseat.getQsb(), grid.inv.numRows + 2, totalRows);
+            addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows);
+            addStaggeredAnimationForView(hotseat.getTaskbarView(), taskbarRow, totalRows);
         }
 
         if (animateOverviewScrim) {
-            PendingAnimation pendingAnimation = new PendingAnimation(ALPHA_DURATION_MS);
+            PendingAnimation pendingAnimation = new PendingAnimation(DURATION_MS);
             addScrimAnimationForState(launcher, NORMAL, pendingAnimation);
             mAnimators.play(pendingAnimation.buildAnim());
         }
 
-        addDepthAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
+        addDepthAnimationForState(launcher, NORMAL, DURATION_MS);
 
         mAnimators.play(launcher.getDragLayer().getSysUiScrim().createSysuiMultiplierAnim(0f, 1f)
-                .setDuration(ALPHA_DURATION_MS));
+                .setDuration(DURATION_MS));
         mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
                 workspace.setClipChildren(workspaceClipChildren);
                 workspace.setClipToPadding(workspaceClipToPadding);
-                cellLayout.setClipChildren(cellLayoutClipChildren);
-                cellLayout.setClipToPadding(cellLayoutClipToPadding);
                 hotseat.setClipChildren(hotseatClipChildren);
                 hotseat.setClipToPadding(hotseatClipToPadding);
             }
         });
     }
 
+    private void addAnimationForPage(CellLayout page, int totalRows) {
+        ShortcutAndWidgetContainer itemsContainer = page.getShortcutsAndWidgets();
+
+        boolean pageClipChildren = page.getClipChildren();
+        boolean pageClipToPadding = page.getClipToPadding();
+
+        page.setClipChildren(false);
+        page.setClipToPadding(false);
+
+        // Set up springs on workspace items.
+        for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
+            View child = itemsContainer.getChildAt(i);
+            CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams());
+            addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows);
+        }
+
+        mAnimators.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                page.setClipChildren(pageClipChildren);
+                page.setClipToPadding(pageClipToPadding);
+            }
+        });
+    }
+
     /**
      * Setup workspace with 0 duration to prepare for our staggered animation.
      */
     private void prepareToAnimate(Launcher launcher, boolean animateOverviewScrim) {
         StateAnimationConfig config = new StateAnimationConfig();
-        config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER
-                | SKIP_TASKBAR;
+        config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW | SKIP_DEPTH_CONTROLLER;
         config.duration = 0;
         // setRecentsAttachedToAppWindow() will animate recents out.
         launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start();
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 8b5d498..6cfe302 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.states.RotationHelper.deltaRotation;
 import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TRANSLATE;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
@@ -36,7 +35,6 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
@@ -79,7 +77,6 @@
     private final boolean mIsRecentsRtl;
 
     private final Rect mTaskRect = new Rect();
-    private final Rect mGridRect = new Rect();
     private boolean mDrawsBelowRecents;
     private final PointF mPivot = new PointF();
     private DeviceProfile mDp;
@@ -98,22 +95,18 @@
     private final FullscreenDrawParams mCurrentFullscreenParams;
     public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
     public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
-    public final AnimatedFloat gridTranslationSecondary = new AnimatedFloat();
 
     // RecentsView properties
     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
     public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
     public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
     public final AnimatedFloat recentsViewPrimaryTranslation = new AnimatedFloat();
-    public final AnimatedFloat gridProgress = new AnimatedFloat();
     private final ScrollState mScrollState = new ScrollState();
 
     // Cached calculations
     private boolean mLayoutValid = false;
     private boolean mScrollValid = false;
     private int mOrientationStateId;
-    private final int mTaskThumbnailPadding;
-    private final int mRowSpacing;
 
     public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
         mContext = context;
@@ -125,8 +118,6 @@
         mOrientationStateId = mOrientationState.getStateId();
         Resources resources = context.getResources();
         mIsRecentsRtl = mOrientationState.getOrientationHandler().getRecentsRtlSetting(resources);
-        mTaskThumbnailPadding = (int) resources.getDimension(R.dimen.task_thumbnail_top_margin);
-        mRowSpacing = (int) resources.getDimension(R.dimen.overview_grid_row_spacing);
     }
 
     /**
@@ -268,7 +259,6 @@
             mOrientationStateId = mOrientationState.getStateId();
 
             getFullScreenScale();
-            mSizeStrategy.calculateGridSize(mContext, mDp, mGridRect);
             mThumbnailData.rotation = mOrientationState.getDisplayRotation();
 
             // mIsRecentsRtl is the inverse of TaskView RTL.
@@ -309,34 +299,6 @@
         mMatrix.postTranslate(insets.left, insets.top);
         mMatrix.postScale(scale, scale);
 
-        // Apply TaskView matrix: gridProgress related properties
-        float interpolatedGridProgress = ACCEL_DEACCEL.getInterpolation(gridProgress.value);
-        final int boxLength = (int) Math.max(taskWidth, taskHeight);
-        float availableHeight = mGridRect.height();
-        float rowHeight = (availableHeight - mRowSpacing) / 2;
-        float gridScale = rowHeight / (boxLength + mTaskThumbnailPadding);
-        scale = Utilities.mapRange(interpolatedGridProgress, 1f, gridScale);
-        mMatrix.postScale(scale, scale, mIsRecentsRtl ? 0 : taskWidth, 0);
-        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
-                Utilities.mapRange(interpolatedGridProgress, 0, gridTranslationSecondary.value));
-
-        // Apply TaskView matrix: task rect and grid rect difference
-        float scaledWidth = taskWidth * gridScale;
-        float taskGridHorizontalDiff;
-        if (mIsRecentsRtl) {
-            float taskRight = mTaskRect.left + scaledWidth;
-            taskGridHorizontalDiff = mGridRect.right - taskRight;
-        } else {
-            float taskLeft = mTaskRect.right - scaledWidth;
-            taskGridHorizontalDiff = mGridRect.left - taskLeft;
-        }
-        float taskGridVerticalDiff =
-                mGridRect.top + mTaskThumbnailPadding * gridScale - mTaskRect.top;
-        mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
-                Utilities.mapRange(interpolatedGridProgress, 0, taskGridHorizontalDiff));
-        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
-                Utilities.mapRange(interpolatedGridProgress, 0, taskGridVerticalDiff));
-
         // Apply TaskView matrix: translate, scroll
         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
         mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index e7101cc..12b59d0 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -21,6 +21,7 @@
 import android.util.FloatProperty;
 import android.widget.Button;
 
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.views.RecentsView.PageCallbacks;
 import com.android.quickstep.views.RecentsView.ScrollState;
@@ -40,30 +41,32 @@
                 }
             };
 
+    private final StatefulActivity mActivity;
     private float mScrollAlpha = 1;
     private float mContentAlpha = 1;
     private float mVisibilityAlpha = 1;
     private float mGridProgress = 1;
 
     private boolean mIsRtl;
-    private final float mOriginalTranslationX, mOriginalTranslationY;
     private float mNormalTranslationPrimary;
     private float mGridTranslationPrimary;
+    private float mGridTranslationSecondary;
+    private float mGridScrollOffset;
+    private float mOffsetTranslationPrimary;
 
-    private int mScrollOffset;
+    private int mSidePadding;
 
     public ClearAllButton(Context context, AttributeSet attrs) {
         super(context, attrs);
         mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-        mOriginalTranslationX = getTranslationX();
-        mOriginalTranslationY = getTranslationY();
+        mActivity = StatefulActivity.fromContext(context);
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
-        mScrollOffset = orientationHandler.getClearAllScrollOffset(getRecentsView(), mIsRtl);
+        mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
     }
 
     private RecentsView getRecentsView() {
@@ -96,25 +99,27 @@
     }
 
     @Override
-    public void onPageScroll(ScrollState scrollState) {
-        PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+    public void onPageScroll(ScrollState scrollState, boolean gridEnabled) {
+        RecentsView recentsView = getRecentsView();
+        if (recentsView == null) {
+            return;
+        }
+
+        PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
         float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
         if (orientationSize == 0) {
             return;
         }
 
-        float shift;
-        if (mIsRtl) {
-            shift = Math.min(scrollState.scrollFromEdge, orientationSize);
-        } else {
-            shift = Math.min(scrollState.scrollFromEdge,
-                    orientationSize + getGridTrans(mGridTranslationPrimary))
-                    - getGridTrans(mGridTranslationPrimary);
+        int leftEdgeScroll = recentsView.getLeftMostChildScroll();
+        float adjustedScrollFromEdge = scrollState.scrollFromEdge - leftEdgeScroll;
+        float shift = Math.min(adjustedScrollFromEdge, orientationSize);
+        mNormalTranslationPrimary = mIsRtl ? -shift : shift;
+        if (!gridEnabled) {
+            mNormalTranslationPrimary += mSidePadding;
         }
-        mNormalTranslationPrimary = mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift);
         applyPrimaryTranslation();
-        orientationHandler.getSecondaryViewTranslate().set(this,
-                orientationHandler.getSecondaryValue(mOriginalTranslationX, mOriginalTranslationY));
+        applySecondaryTranslation();
         mScrollAlpha = 1 - shift / orientationSize;
         updateAlpha();
     }
@@ -130,11 +135,26 @@
         applyPrimaryTranslation();
     }
 
+    public void setGridTranslationSecondary(float gridTranslationSecondary) {
+        mGridTranslationSecondary = gridTranslationSecondary;
+        applyPrimaryTranslation();
+    }
+
+    public void setGridScrollOffset(float gridScrollOffset) {
+        mGridScrollOffset = gridScrollOffset;
+    }
+
+    public void setOffsetTranslationPrimary(float offsetTranslationPrimary) {
+        mOffsetTranslationPrimary = offsetTranslationPrimary;
+        applyPrimaryTranslation();
+    }
+
     public float getScrollAdjustment(boolean gridEnabled) {
         float scrollAdjustment = 0;
         if (gridEnabled) {
-            scrollAdjustment += mGridTranslationPrimary;
+            scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
         }
+        scrollAdjustment += mOffsetTranslationPrimary;
         return scrollAdjustment;
     }
 
@@ -160,10 +180,31 @@
 
         PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
         orientationHandler.getPrimaryViewTranslate().set(this,
-                mNormalTranslationPrimary + getGridTrans(mGridTranslationPrimary));
+                orientationHandler.getPrimaryValue(0f, getOriginalTranslationY())
+                        + mNormalTranslationPrimary + mOffsetTranslationPrimary + getGridTrans(
+                        mGridTranslationPrimary));
+    }
+
+    private void applySecondaryTranslation() {
+        RecentsView recentsView = getRecentsView();
+        if (recentsView == null) {
+            return;
+        }
+
+        PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
+        orientationHandler.getSecondaryViewTranslate().set(this,
+                orientationHandler.getSecondaryValue(0f, getOriginalTranslationY())
+                        + getGridTrans(mGridTranslationSecondary));
     }
 
     private float getGridTrans(float endTranslation) {
         return mGridProgress > 0 ? endTranslation : 0;
     }
+
+    /**
+     * Get the Y translation that is set in the original layout position, before scrolling.
+     */
+    private float getOriginalTranslationY() {
+        return mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx / 2.0f;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
index 7cc00b7..ed642df 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.java
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -21,26 +21,14 @@
 import android.util.AttributeSet;
 import android.view.View;
 
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.FastBitmapDrawable;
-
-import java.util.ArrayList;
-
 /**
  * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
  * when the drawable changes.
  */
 public class IconView extends View {
 
-    public interface OnScaleUpdateListener {
-        public void onScaleUpdate(float scale);
-    }
-
     private Drawable mDrawable;
 
-    private ArrayList<OnScaleUpdateListener> mScaleListeners;
-
     public IconView(Context context) {
         super(context);
     }
@@ -94,16 +82,6 @@
     }
 
     @Override
-    public void invalidateDrawable(@NonNull Drawable drawable) {
-        super.invalidateDrawable(drawable);
-        if (drawable instanceof FastBitmapDrawable && mScaleListeners != null) {
-            for (OnScaleUpdateListener listener : mScaleListeners) {
-                listener.onScaleUpdate(((FastBitmapDrawable) drawable).getScale());
-            }
-        }
-    }
-
-    @Override
     protected void onDraw(Canvas canvas) {
         if (mDrawable != null) {
             mDrawable.draw(canvas);
@@ -115,22 +93,6 @@
         return false;
     }
 
-    public void addUpdateScaleListener(OnScaleUpdateListener listener) {
-        if (mScaleListeners == null) {
-            mScaleListeners = new ArrayList<>();
-        }
-        mScaleListeners.add(listener);
-        if (mDrawable instanceof FastBitmapDrawable) {
-            listener.onScaleUpdate(((FastBitmapDrawable) mDrawable).getScale());
-        }
-    }
-
-    public void removeUpdateScaleListener(OnScaleUpdateListener listener) {
-        if (mScaleListeners != null) {
-            mScaleListeners.remove(listener);
-        }
-    }
-
     @Override
     public void setAlpha(float alpha) {
         super.setAlpha(alpha);
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 1241982..6fcd54c 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -29,6 +29,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -144,6 +145,7 @@
     public void setInsets(Rect insets) {
         mInsets.set(insets);
         updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+        updateHorizontalPadding();
     }
 
     public void updateHiddenFlags(@ActionsHiddenFlags int visibilityFlags, boolean enable) {
@@ -187,6 +189,10 @@
         return mMultiValueAlpha.getProperty(INDEX_FULLSCREEN_ALPHA);
     }
 
+    private void updateHorizontalPadding() {
+        setPadding(mInsets.left, 0, mInsets.right, 0);
+    }
+
     /** Updates vertical margins for different navigation mode or configuration changes. */
     public void updateVerticalMargin(Mode mode) {
         LayoutParams actionParams = (LayoutParams) findViewById(
@@ -196,6 +202,13 @@
                 getBottomVerticalMargin(mode));
     }
 
+    /**
+     * Set the device profile for this view to draw with.
+     */
+    public void setDp(DeviceProfile dp) {
+        requestLayout();
+    }
+
     protected int getBottomVerticalMargin(Mode mode) {
         int bottomMargin;
         int orientation = getResources().getConfiguration().orientation;
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f216985..a35580f 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
@@ -34,6 +35,7 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
@@ -50,6 +52,7 @@
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.LayoutTransition;
 import android.animation.LayoutTransition.TransitionListener;
@@ -61,6 +64,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
+import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -129,8 +133,11 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.ViewUtils;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.util.SurfaceTransactionApplier;
@@ -142,6 +149,8 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.wm.shell.pip.IPipAnimationListener;
 
@@ -301,6 +310,7 @@
     protected final Rect mTempRect = new Rect();
     protected final RectF mTempRectF = new RectF();
     private final PointF mTempPointF = new PointF();
+    private float mFullscreenScale;
 
     private static final int DISMISS_TASK_DURATION = 300;
     private static final int ADDITION_TASK_DURATION = 200;
@@ -310,11 +320,14 @@
     protected final T mActivity;
     private final float mFastFlingVelocity;
     private final RecentsModel mModel;
-    private final int mTaskTopMargin;
     private final int mRowSpacing;
     private final ClearAllButton mClearAllButton;
     private final Rect mClearAllButtonDeadZoneRect = new Rect();
     private final Rect mTaskViewDeadZoneRect = new Rect();
+    /**
+     * Reflects if Recents is currently in the middle of a gesture
+     */
+    private boolean mGestureActive;
 
     private final ScrollState mScrollState = new ScrollState();
     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
@@ -342,6 +355,8 @@
     // The GestureEndTarget that is still in progress.
     private GestureState.GestureEndTarget mCurrentGestureEndTarget;
 
+    IntSet mTopIdSet = new IntSet();
+
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
      */
@@ -525,8 +540,6 @@
 
         mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
-        mTaskTopMargin = getResources()
-                .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
         mRowSpacing = getResources().getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
         mSquaredTouchSlop = squaredTouchSlop(context);
 
@@ -624,8 +637,8 @@
             return;
         }
         mModel.getIconCache().clear();
-        unloadVisibleTaskData();
-        loadVisibleTaskData();
+        unloadVisibleTaskData(TaskView.FLAG_UPDATE_ICON);
+        loadVisibleTaskData(TaskView.FLAG_UPDATE_ICON);
     }
 
     public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
@@ -713,6 +726,72 @@
         super.draw(canvas);
     }
 
+    public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
+        if (mRunningTaskId != -1 && mRunningTaskId == taskId &&
+                getLiveTileParams().getTargetSet().findTask(taskId) != null) {
+            launchSideTaskInLiveTileMode(taskId, getLiveTileParams().getTargetSet().apps);
+        }
+    }
+
+    public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps) {
+        AnimatorSet anim = new AnimatorSet();
+        TaskView taskView = getTaskView(taskId);
+        if (taskView == null || !isTaskViewVisible(taskView)) {
+            // TODO: Refine this animation.
+            SurfaceTransactionApplier surfaceApplier =
+                    new SurfaceTransactionApplier(mActivity.getDragLayer());
+            ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
+            appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
+            appAnimator.setInterpolator(ACCEL_DEACCEL);
+            appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+                @Override
+                public void onUpdate(float percent) {
+                    SurfaceParams.Builder builder = new SurfaceParams.Builder(
+                            apps[apps.length - 1].leash);
+                    Matrix matrix = new Matrix();
+                    matrix.postScale(percent, percent);
+                    matrix.postTranslate(mActivity.getDeviceProfile().widthPx * (1 - percent) / 2,
+                            mActivity.getDeviceProfile().heightPx * (1 - percent) / 2);
+                    builder.withAlpha(percent).withMatrix(matrix);
+                    surfaceApplier.scheduleApply(builder.build());
+                }
+            });
+            anim.play(appAnimator);
+        } else {
+            TaskViewUtils.composeRecentsLaunchAnimator(
+                    anim, taskView, apps,
+                    mLiveTileParams.getTargetSet().wallpapers, true /* launcherClosing */,
+                    mActivity.getStateManager(), this,
+                    getDepthController());
+        }
+        anim.addListener(new AnimatorListenerAdapter(){
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                cleanUp(false);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animator) {
+                cleanUp(true);
+            }
+
+            private void cleanUp(boolean canceled) {
+                if (mRecentsAnimationController != null) {
+                    mRecentsAnimationController.finish(false /* toRecents */,
+                            null /* onFinishComplete */);
+                    if (canceled) {
+                        mRecentsAnimationController = null;
+                    } else {
+                        mSizeStrategy.onLaunchTaskSuccess();
+                    }
+                    ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+                }
+            }
+        });
+        anim.start();
+    }
+
     private void updateTaskStartIndex(View affectingView) {
         if (!(affectingView instanceof TaskView) && !(affectingView instanceof ClearAllButton)) {
             int childCount = getChildCount();
@@ -740,7 +819,7 @@
         int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
                 mOverviewFullscreenEnabled, showAsGrid());
         int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
-                mOverviewFullscreenEnabled, showAsGrid()));
+                mOverviewFullscreenEnabled));
         int taskEnd = taskStart + taskSize;
         return (taskStart >= start && taskStart <= end) || (taskEnd >= start
                 && taskEnd <= end);
@@ -908,7 +987,7 @@
         }
 
         // Unload existing visible task data
-        unloadVisibleTaskData();
+        unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
 
         TaskView ignoreResetTaskView =
                 mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
@@ -1018,7 +1097,6 @@
             mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
             mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
             mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
-            mLiveTileTaskViewSimulator.gridProgress.value = 0;
         }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1031,7 +1109,7 @@
 
         updateCurveProperties();
         // Update the set of visible task's data
-        loadVisibleTaskData();
+        loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
         setTaskModalness(0);
     }
 
@@ -1062,7 +1140,9 @@
     public void setInsets(Rect insets) {
         mInsets.set(insets);
         resetPaddingFromTaskSize();
-        mLiveTileTaskViewSimulator.setDp(mActivity.getDeviceProfile());
+        DeviceProfile dp = mActivity.getDeviceProfile();
+        mLiveTileTaskViewSimulator.setDp(dp);
+        mActionsView.setDp(dp);
     }
 
     private void resetPaddingFromTaskSize() {
@@ -1071,7 +1151,7 @@
         mTaskWidth = mTempRect.width();
         mTaskHeight = mTempRect.height();
 
-        mTempRect.top -= mTaskTopMargin;
+        mTempRect.top -= dp.overviewTaskThumbnailTopMarginPx;
         setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
                 dp.widthPx - mInsets.right - mTempRect.right,
                 dp.heightPx - mInsets.bottom - mTempRect.bottom);
@@ -1088,6 +1168,10 @@
      */
     private void updateTaskSize() {
         final int taskCount = getTaskViewCount();
+        if (taskCount == 0) {
+            return;
+        }
+
         float accumulatedTranslationX = 0;
         float[] fullscreenTranslations = new float[taskCount];
         int firstNonHomeTaskIndex = 0;
@@ -1102,8 +1186,11 @@
 
             taskView.updateTaskSize();
             fullscreenTranslations[i] += accumulatedTranslationX;
+            // Compensate space caused by TaskView scaling.
             float widthDiff =
                     taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
+            // Compensate page spacing widening caused by RecentsView scaling.
+            widthDiff += mPageSpacing * (1 - 1 / mFullscreenScale);
             float fullscreenTranslationX = mIsRtl ? widthDiff : -widthDiff;
             fullscreenTranslations[i] += fullscreenTranslationX;
             accumulatedTranslationX += fullscreenTranslationX;
@@ -1116,7 +1203,7 @@
                     fullscreenTranslations[i] - fullscreenTranslations[firstNonHomeTaskIndex]);
         }
 
-        updateGridProperties();
+        updateGridProperties(false);
     }
 
     public void getTaskSize(Rect outRect) {
@@ -1147,7 +1234,7 @@
             }
 
             // After scrolling, update the visible task's data
-            loadVisibleTaskData();
+            loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
         }
 
         // Update the high res thumbnail loader state
@@ -1177,13 +1264,13 @@
             View page = getPageAt(i);
             mScrollState.updateInterpolation(mActivity.getDeviceProfile(),
                     mOrientationHandler.getChildStartWithTranslation(page));
-            ((PageCallbacks) page).onPageScroll(mScrollState);
+            ((PageCallbacks) page).onPageScroll(mScrollState, mOverviewGridEnabled);
         }
     }
 
     @Override
     protected int getDestinationPage(int scaledScroll) {
-        if (mGridProgress == 0) {
+        if (!showAsGrid()) {
             return super.getDestinationPage(scaledScroll);
         }
 
@@ -1210,7 +1297,7 @@
      * Iterates through all the tasks, and loads the associated task data for newly visible tasks,
      * and unloads the associated task data for tasks that are no longer visible.
      */
-    public void loadVisibleTaskData() {
+    public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
         if (!mOverviewStateEnabled || mTaskListChangeId == -1) {
             // Skip loading visible task data if we've already left the overview state, or if the
             // task list hasn't been loaded yet (the task views will not reflect the task list)
@@ -1252,12 +1339,18 @@
                     continue;
                 }
                 if (!mHasVisibleTaskData.get(task.key.id)) {
-                    taskView.onTaskListVisibilityChanged(true /* visible */);
+                    // Ignore thumbnail update if it's current running task during the gesture
+                    // We snapshot at end of gesture, it will update then
+                    int changes = dataChanges;
+                    if (taskView == getRunningTaskView() && mGestureActive) {
+                        changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL;
+                    }
+                    taskView.onTaskListVisibilityChanged(true /* visible */, changes);
                 }
                 mHasVisibleTaskData.put(task.key.id, visible);
             } else {
                 if (mHasVisibleTaskData.get(task.key.id)) {
-                    taskView.onTaskListVisibilityChanged(false /* visible */);
+                    taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
                 }
                 mHasVisibleTaskData.delete(task.key.id);
             }
@@ -1267,12 +1360,12 @@
     /**
      * Unloads any associated data from the currently visible tasks
      */
-    private void unloadVisibleTaskData() {
+    private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
             if (mHasVisibleTaskData.valueAt(i)) {
                 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
                 if (taskView != null) {
-                    taskView.onTaskListVisibilityChanged(false /* visible */);
+                    taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
                 }
             }
         }
@@ -1310,7 +1403,7 @@
         mRecentsAnimationController = null;
         mLiveTileParams.setTargetSet(null);
 
-        unloadVisibleTaskData();
+        unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
         setCurrentPage(0);
         mDwbToastShown = false;
         mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
@@ -1358,6 +1451,7 @@
      * Called when a gesture from an app is starting.
      */
     public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
+        mGestureActive = true;
         // This needs to be called before the other states are set since it can create the task view
         if (mOrientationState.setGestureActive(true)) {
             updateOrientationHandler();
@@ -1428,6 +1522,7 @@
      * Called when a gesture from an app has finished, and the animation to the target has ended.
      */
     public void onGestureAnimationEnd() {
+        mGestureActive = false;
         if (mOrientationState.setGestureActive(false)) {
             updateOrientationHandler();
         }
@@ -1581,17 +1676,14 @@
      * This method only calculates the potential position and depends on {@link #setGridProgress} to
      * apply the actual scaling and translation.
      */
-    private void updateGridProperties() {
+    private void updateGridProperties(boolean isTaskDismissal) {
         int taskCount = getTaskViewCount();
         if (taskCount == 0) {
             return;
         }
 
         final int boxLength = Math.max(mTaskWidth, mTaskHeight);
-        float availableHeight = mLastComputedGridSize.height();
-        float rowHeight = (availableHeight - mRowSpacing) / 2;
-        float gridScale = rowHeight / (boxLength + mTaskTopMargin);
-
+        int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
         int topRowWidth = 0;
         int bottomRowWidth = 0;
         float topAccumulatedTranslationX = 0;
@@ -1600,6 +1692,10 @@
         IntSet bottomSet = new IntSet();
         float[] gridTranslations = new float[taskCount];
         int firstNonHomeTaskIndex = 0;
+
+        if (!isTaskDismissal) {
+            mTopIdSet.clear();
+        }
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = getTaskViewAt(i);
             if (isHomeTask(taskView)) {
@@ -1609,43 +1705,21 @@
                 continue;
             }
 
-            taskView.setGridScale(gridScale);
-
-            float scaledWidth = taskView.getLayoutParams().width * gridScale;
-            float taskGridHorizontalDiff;
-            if (mIsRtl) {
-                float taskRight = mLastComputedTaskSize.left + scaledWidth;
-                taskGridHorizontalDiff = mLastComputedGridSize.right - taskRight;
-            } else {
-                float taskLeft = mLastComputedTaskSize.right - scaledWidth;
-                taskGridHorizontalDiff = mLastComputedGridSize.left - taskLeft;
-            }
-            gridTranslations[i] -= taskGridHorizontalDiff;
-            taskView.setGridOffsetTranslationX(taskGridHorizontalDiff);
-
-            float taskGridVerticalDiff = mLastComputedGridSize.top + mTaskTopMargin * gridScale
-                    - mLastComputedTaskSize.top;
-
-            // Off-set gap due to task scaling.
-            float widthDiff = taskView.getLayoutParams().width * (1 - gridScale);
-            float gridScaleTranslationX = mIsRtl ? widthDiff : -widthDiff;
-            gridTranslations[i] += gridScaleTranslationX;
-
-            // Visible offset caused by having scaling pivot on top-right.
-            taskView.setNonRtlVisibleOffset(mIsRtl ? 0 : widthDiff);
-
-            if (topRowWidth <= bottomRowWidth) {
+            // Evenly distribute tasks between rows unless rearranging due to task dismissal, in
+            // which case keep tasks in their respective rows.
+            if ((!isTaskDismissal && topRowWidth <= bottomRowWidth) || (isTaskDismissal && mTopIdSet
+                    .contains(taskView.getTask().key.id))) {
                 gridTranslations[i] += topAccumulatedTranslationX;
-                topRowWidth += taskView.getLayoutParams().width * gridScale + mPageSpacing;
+                topRowWidth += taskView.getLayoutParams().width + mPageSpacing;
                 topSet.add(i);
+                mTopIdSet.add(taskView.getTask().key.id);
 
-                taskView.setGridTranslationY(taskGridVerticalDiff);
+                taskView.setGridTranslationY(0);
 
                 // Move horizontally into empty space.
                 float widthOffset = 0;
                 for (int j = i - 1; bottomSet.contains(j); j--) {
-                    widthOffset += getTaskViewAt(j).getLayoutParams().width * gridScale
-                            + mPageSpacing;
+                    widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
                 }
 
                 float gridTranslationX = mIsRtl ? widthOffset : -widthOffset;
@@ -1653,26 +1727,39 @@
                 topAccumulatedTranslationX += gridTranslationX;
             } else {
                 gridTranslations[i] += bottomAccumulatedTranslationX;
-                bottomRowWidth += taskView.getLayoutParams().width * gridScale + mPageSpacing;
+                bottomRowWidth += taskView.getLayoutParams().width + mPageSpacing;
                 bottomSet.add(i);
 
                 // Move into bottom row.
-                float heightOffset = (boxLength + mTaskTopMargin) * gridScale + mRowSpacing;
-                taskView.setGridTranslationY(heightOffset + taskGridVerticalDiff);
+                float heightOffset = (boxLength + taskTopMargin) + mRowSpacing;
+                taskView.setGridTranslationY(heightOffset);
 
                 // Move horizontally into empty space.
                 float widthOffset = 0;
                 for (int j = i - 1; topSet.contains(j); j--) {
-                    widthOffset += getTaskViewAt(j).getLayoutParams().width * gridScale
-                            + mPageSpacing;
+                    widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing;
                 }
 
                 float gridTranslationX = mIsRtl ? widthOffset : -widthOffset;
                 gridTranslations[i] += gridTranslationX;
                 bottomAccumulatedTranslationX += gridTranslationX;
             }
-            topAccumulatedTranslationX += gridScaleTranslationX;
-            bottomAccumulatedTranslationX += gridScaleTranslationX;
+        }
+
+        // If the first non-home task does not take full width of task Rect, shift all tasks
+        // accordingly without affecting scrolls.
+        int firstTaskWidth = getTaskViewAt(firstNonHomeTaskIndex).getLayoutParams().width;
+        float firstNonHomeTaskOffset = firstTaskWidth == ViewGroup.LayoutParams.MATCH_PARENT ? 0
+                : mTaskWidth - firstTaskWidth;
+        float offsetTranslation = mIsRtl ? firstNonHomeTaskOffset : -firstNonHomeTaskOffset;
+
+        // We need to maintain first non-home task's grid translation at 0, now shift translation
+        // of all the TaskViews to achieve that.
+        for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+            TaskView taskView = getTaskViewAt(i);
+            taskView.setGridTranslationX(
+                    gridTranslations[i] - gridTranslations[firstNonHomeTaskIndex]);
+            taskView.setGridOffsetTranslationX(offsetTranslation);
         }
 
         // Use the accumulated translation of the longer row.
@@ -1688,7 +1775,7 @@
                 shorterRowCompensation = bottomRowWidth - topRowWidth;
             }
         } else {
-            if (!topSet.contains(taskCount - 1)) {
+            if (bottomSet.contains(taskCount - 1)) {
                 shorterRowCompensation = topRowWidth - bottomRowWidth;
             }
         }
@@ -1709,14 +1796,14 @@
                 clearAllAccumulatedTranslation + clearAllShorterRowCompensation
                         + clearAllShortTotalCompensation;
 
-        // We need to maintain first non-home task's grid translation at 0, now shift translation
-        // of all the TaskViews to achieve that.
-        for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
-            getTaskViewAt(i).setGridTranslationX(
-                    gridTranslations[i] - gridTranslations[firstNonHomeTaskIndex]);
-        }
         mClearAllButton.setGridTranslationPrimary(
                 clearAllTotalTranslationX - gridTranslations[firstNonHomeTaskIndex]);
+        mClearAllButton.setGridTranslationSecondary(
+                boxLength - mTaskHeight / 2f + mRowSpacing / 2f);
+        mClearAllButton.setGridScrollOffset(
+                mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left
+                        : mLastComputedTaskSize.right - mLastComputedGridSize.right);
+        mClearAllButton.setOffsetTranslationPrimary(offsetTranslation);
 
         setGridProgress(mGridProgress);
     }
@@ -1741,7 +1828,6 @@
         for (int i = 0; i < taskCount; i++) {
             getTaskViewAt(i).setGridProgress(gridProgress);
         }
-        mLiveTileTaskViewSimulator.gridProgress.value = gridProgress;
         mClearAllButton.setGridProgress(gridProgress);
     }
 
@@ -1789,8 +1875,10 @@
 
         /**
          * Updates the page UI based on scroll params.
+         *
+         * @param gridEnabled whether Overveiw is currently showing as 2 rows grid
          */
-        default void onPageScroll(ScrollState scrollState) {}
+        default void onPageScroll(ScrollState scrollState, boolean gridEnabled) {}
     }
 
     public static class ScrollState extends CurveProperties {
@@ -1988,7 +2076,7 @@
                     } else {
                         snapToPageImmediately(pageToSnapTo);
                         // Grid got messed up, reapply.
-                        updateGridProperties();
+                        updateGridProperties(true);
                     }
                     // Update the layout synchronously so that the position of next view is
                     // immediately available.
@@ -2285,7 +2373,7 @@
 
         // Update the pivots such that when the task is scaled, it fills the full page
         getTaskSize(mTempRect);
-        getPagedViewOrientedState().getFullScreenScaleAndPivot(
+        mFullscreenScale = getPagedViewOrientedState().getFullScreenScaleAndPivot(
                 mTempRect, mActivity.getDeviceProfile(), mTempPointF);
         setPivotX(mTempPointF.x);
         setPivotY(mTempPointF.y);
@@ -2473,7 +2561,7 @@
             if (child == mSplitHiddenTaskView) {
 
                 int left = newScroll[i] + getPaddingStart();
-                int topMargin = mSplitHiddenTaskView.getThumbnailTopMargin();
+                int topMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
                 int top = -mSplitHiddenTaskView.getHeight() - locationOnScreen[1];
                 mSplitHiddenTaskView.layout(left, top,
                         left + mSplitHiddenTaskView.getWidth(),
@@ -2733,7 +2821,7 @@
     @Override
     protected void notifyPageSwitchListener(int prevPage) {
         super.notifyPageSwitchListener(prevPage);
-        loadVisibleTaskData();
+        loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
         updateEnabledOverlays();
     }
 
@@ -2846,22 +2934,42 @@
     @Override
     protected int computeMinScroll() {
         if (getTaskViewCount() > 0) {
-            if (mDisallowScrollToClearAll) {
+            if (mIsRtl && mDisallowScrollToClearAll) {
                 // We aren't showing the clear all button,
                 // so use the leftmost task as the min scroll.
-                if (mIsRtl) {
-                    return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
-                }
-                return getScrollForPage(mTaskViewStartIndex);
+                return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
             }
-            if (mIsRtl) {
-                return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
-            }
-            return getScrollForPage(mTaskViewStartIndex);
+            return getLeftMostChildScroll();
         }
         return super.computeMinScroll();
     }
 
+    /**
+     * Returns page scroll of the left most child.
+     */
+    public int getLeftMostChildScroll() {
+        if (mIsRtl) {
+            return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
+        }
+        return getScrollForPage(mTaskViewStartIndex);
+    }
+
+    @Override
+    protected int computeMaxScroll() {
+        if (getTaskViewCount() > 0) {
+            if (!mIsRtl && mDisallowScrollToClearAll) {
+                // We aren't showing the clear all button,
+                // so use the rightmost task as the min scroll.
+                return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
+            }
+            if (mIsRtl) {
+                return getScrollForPage(mTaskViewStartIndex);
+            }
+            return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
+        }
+        return super.computeMaxScroll();
+    }
+
     @Override
     protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
             ComputePageScrollsLogic scrollLogic) {
@@ -2907,26 +3015,7 @@
             return super.getChildVisibleSize(index);
         }
         return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
-                mOverviewFullscreenEnabled, showAsGrid()));
-    }
-
-    @Override
-    protected int computeMaxScroll() {
-        if (getTaskViewCount() > 0) {
-            if (mDisallowScrollToClearAll) {
-                // We aren't showing the clear all button,
-                // so use the rightmost task as the min scroll.
-                if (mIsRtl) {
-                    return getScrollForPage(mTaskViewStartIndex);
-                }
-                return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)));
-            }
-            if (mIsRtl) {
-                return getScrollForPage(mTaskViewStartIndex);
-            }
-            return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
-        }
-        return super.computeMaxScroll();
+                mOverviewFullscreenEnabled));
     }
 
     public ClearAllButton getClearAllButton() {
@@ -3148,7 +3237,9 @@
             MAIN_EXECUTOR.execute(() -> {
                 // Needed for activities that auto-enter PiP, which will not trigger a remote
                 // animation to be created
-                mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+                if (mActivity != null) {
+                    mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+                }
             });
         }
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index a5b7a5b..a46daf3 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -58,7 +58,6 @@
     private static final int REVEAL_OPEN_DURATION = 150;
     private static final int REVEAL_CLOSE_DURATION = 100;
 
-    private final float mThumbnailTopMargin;
     private BaseDraggingActivity mActivity;
     private TextView mTaskName;
     private AnimatorSet mOpenCloseAnimator;
@@ -73,7 +72,6 @@
         super(context, attrs, defStyleAttr);
 
         mActivity = BaseDraggingActivity.fromContext(context);
-        mThumbnailTopMargin = getResources().getDimension(R.dimen.task_thumbnail_top_margin);
         setClipToOutline(true);
     }
 
@@ -123,14 +121,15 @@
     }
 
     public void setPosition(float x, float y, PagedOrientationHandler pagedOrientationHandler) {
-        float adjustedY = y + mThumbnailTopMargin;
+        int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
+        float adjustedY = y + taskTopMargin;
         // Changing pivot to make computations easier
         // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
         // which would render the X and Y position set here incorrect
         setPivotX(0);
         if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
             // In tablet, set pivotY to original position without mThumbnailTopMargin adjustment.
-            setPivotY(-mThumbnailTopMargin);
+            setPivotY(-taskTopMargin);
         } else {
             setPivotY(0);
         }
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 2b54f95..af62582 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -440,13 +440,14 @@
     }
 
     /**
-     * Returns whether the snapshot is real.
+     * Returns whether the snapshot is real. If the device is locked for the user of the task,
+     * the snapshot used will be an app-theme generated snapshot instead of a real snapshot.
      */
     public boolean isRealSnapshot() {
         if (mThumbnailData == null) {
             return false;
         }
-        return mThumbnailData.isRealSnapshot;
+        return mThumbnailData.isRealSnapshot && !mTask.isLocked;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2b7e6fd..d497a96 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -42,6 +42,8 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -67,6 +69,7 @@
 import android.widget.FrameLayout;
 import android.widget.Toast;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.DeviceProfile;
@@ -106,6 +109,7 @@
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 
+import java.lang.annotation.Retention;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
@@ -117,6 +121,19 @@
 
     private static final String TAG = TaskView.class.getSimpleName();
 
+    public static final int FLAG_UPDATE_ICON = 1;
+    public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
+
+    public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL;
+
+    /**
+     * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more
+     * granularity on which components of this task require an update
+     */
+    @Retention(SOURCE)
+    @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
+    public @interface TaskDataChanges {}
+
     /**
      * The alpha of a black scrim on a page in the carousel as it leaves the screen.
      * In the resting position of the carousel, the adjacent pages have about half this scrim.
@@ -262,7 +279,6 @@
     private float mFullscreenProgress;
     private float mGridProgress;
     private float mFullscreenScale = 1;
-    private float mGridScale = 1;
     private final FullscreenDrawParams mCurrentFullscreenParams;
     private final StatefulActivity mActivity;
 
@@ -281,7 +297,6 @@
     private float mGridTranslationY;
     // Offset translation does not affect scroll calculation.
     private float mGridOffsetTranslationX;
-    private float mNonRtlVisibleOffset;
 
     private ObjectAnimator mIconAndDimAnimator;
     private float mIconScaleAnimStartProgress = 0;
@@ -359,7 +374,8 @@
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
 
-        mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams);
+        mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams,
+                mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
         setOutlineProvider(mOutlineProvider);
     }
 
@@ -557,7 +573,19 @@
         }
     }
 
+    /**
+     * See {@link TaskDataChanges}
+     * @param visible If this task view will be visible to the user in overview or hidden
+     */
     public void onTaskListVisibilityChanged(boolean visible) {
+        onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL);
+    }
+
+    /**
+     * See {@link TaskDataChanges}
+     * @param visible If this task view will be visible to the user in overview or hidden
+     */
+    public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {
         if (mTask == null) {
             return;
         }
@@ -568,22 +596,37 @@
             RecentsModel model = RecentsModel.INSTANCE.get(getContext());
             TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
             TaskIconCache iconCache = model.getIconCache();
-            mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
-                    mTask, thumbnail -> mSnapshotView.setThumbnail(mTask, thumbnail));
-            mIconLoadRequest = iconCache.updateIconInBackground(mTask,
-                    (task) -> {
-                        setIcon(task.icon);
-                        mDigitalWellBeingToast.initialize(mTask);
-                    });
+
+            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+                mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
+                        mTask, thumbnail -> {
+                            mSnapshotView.setThumbnail(mTask, thumbnail);
+                        });
+            }
+            if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+                mIconLoadRequest = iconCache.updateIconInBackground(mTask,
+                        (task) -> {
+                            setIcon(task.icon);
+                            mDigitalWellBeingToast.initialize(mTask);
+                        });
+            }
         } else {
-            mSnapshotView.setThumbnail(null, null);
-            setIcon(null);
-            // Reset the task thumbnail reference as well (it will be fetched from the cache or
-            // reloaded next time we need it)
-            mTask.thumbnail = null;
+            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+                mSnapshotView.setThumbnail(null, null);
+                // Reset the task thumbnail reference as well (it will be fetched from the cache or
+                // reloaded next time we need it)
+                mTask.thumbnail = null;
+            }
+            if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+                setIcon(null);
+            }
         }
     }
 
+    private boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
+        return (dataChange & flag) == flag;
+    }
+
     private void cancelPendingLoadTasks() {
         if (mThumbnailLoadRequest != null) {
             mThumbnailLoadRequest.cancel();
@@ -629,17 +672,14 @@
         }
     }
 
-    public int getThumbnailTopMargin() {
-        return (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
-    }
-
     public void setOrientationState(RecentsOrientedState orientationState) {
         PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
         boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
         LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
-        int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
-        int taskIconMargin = (int) getResources().getDimension(R.dimen.task_icon_top_margin);
-        int taskIconHeight = (int) getResources().getDimension(R.dimen.task_thumbnail_icon_size);
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+        snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+        int taskIconMargin = deviceProfile.overviewTaskMarginPx;
+        int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
         switch (orientationHandler.getRotation()) {
             case ROTATION_90:
@@ -650,7 +690,7 @@
                 break;
             case ROTATION_180:
                 iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
-                iconParams.bottomMargin = -thumbnailPadding;
+                iconParams.bottomMargin = -snapshotParams.topMargin;
                 iconParams.leftMargin = iconParams.rightMargin = 0;
                 iconParams.topMargin = taskIconMargin;
                 break;
@@ -667,8 +707,12 @@
                 iconParams.topMargin = taskIconMargin;
                 break;
         }
+        mSnapshotView.setLayoutParams(snapshotParams);
+        iconParams.width = iconParams.height = taskIconHeight;
         mIconView.setLayoutParams(iconParams);
         mIconView.setRotation(orientationHandler.getDegreesRotated());
+        snapshotParams.topMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+        mSnapshotView.setLayoutParams(snapshotParams);
 
         if (mMenuView != null) {
             mMenuView.onRotationChanged();
@@ -748,8 +792,8 @@
 
     @Override
     public void onRecycle() {
-        mFullscreenTranslationX = mGridTranslationX = mGridTranslationY =
-                mGridOffsetTranslationX = mBoxTranslationY = mNonRtlVisibleOffset = 0f;
+        mFullscreenTranslationX = mGridTranslationX =
+                mGridTranslationY = mGridOffsetTranslationX = mBoxTranslationY = 0f;
         resetViewTransforms();
         // Clear any references to the thumbnail (it will be re-read either from the cache or the
         // system on next bind)
@@ -759,7 +803,7 @@
     }
 
     @Override
-    public void onPageScroll(ScrollState scrollState) {
+    public void onPageScroll(ScrollState scrollState, boolean gridEnabled) {
         // Don't do anything if it's modal.
         if (mModalness > 0) {
             return;
@@ -862,11 +906,6 @@
         return mFullscreenScale;
     }
 
-    public void setGridScale(float gridScale) {
-        mGridScale = gridScale;
-        applyScale();
-    }
-
     /**
      * Moves TaskView between carousel and 2 row grid.
      *
@@ -883,8 +922,6 @@
         float scale = 1;
         float fullScreenProgress = EXAGGERATED_EASE.getInterpolation(mFullscreenProgress);
         scale *= Utilities.mapRange(fullScreenProgress, 1f, mFullscreenScale);
-        float gridProgress = ACCEL_DEACCEL.getInterpolation(mGridProgress);
-        scale *= Utilities.mapRange(gridProgress, 1f, mGridScale);
         setScaleX(scale);
         setScaleY(scale);
     }
@@ -947,10 +984,6 @@
         applyTranslationX();
     }
 
-    public void setNonRtlVisibleOffset(float nonRtlVisibleOffset) {
-        mNonRtlVisibleOffset = nonRtlVisibleOffset;
-    }
-
     public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
         float scrollAdjustment = 0;
         if (fullscreenEnabled) {
@@ -962,22 +995,19 @@
         return scrollAdjustment;
     }
 
-    public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
+    public float getOffsetAdjustment(boolean fullscreenEnabled,boolean gridEnabled) {
         float offsetAdjustment = getScrollAdjustment(fullscreenEnabled, gridEnabled);
         if (gridEnabled) {
-            offsetAdjustment += mGridOffsetTranslationX + mNonRtlVisibleOffset;
+            offsetAdjustment += mGridOffsetTranslationX;
         }
         return offsetAdjustment;
     }
 
-    public float getSizeAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
+    public float getSizeAdjustment(boolean fullscreenEnabled) {
         float sizeAdjustment = 1;
         if (fullscreenEnabled) {
             sizeAdjustment *= mFullscreenScale;
         }
-        if (gridEnabled) {
-            sizeAdjustment *= mGridScale;
-        }
         return sizeAdjustment;
     }
 
@@ -1039,17 +1069,17 @@
 
     private static final class TaskOutlineProvider extends ViewOutlineProvider {
 
-        private final int mMarginTop;
+        private int mMarginTop;
         private FullscreenDrawParams mFullscreenParams;
 
-        TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams) {
-            mMarginTop = context.getResources().getDimensionPixelSize(
-                    R.dimen.task_thumbnail_top_margin);
+        TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams, int topMargin) {
+            mMarginTop = topMargin;
             mFullscreenParams = fullscreenParams;
         }
 
-        public void setFullscreenParams(FullscreenDrawParams params) {
+        public void updateParams(FullscreenDrawParams params, int topMargin) {
             mFullscreenParams = params;
+            mMarginTop = topMargin;
         }
 
         @Override
@@ -1172,7 +1202,9 @@
         }
 
         thumbnail.setFullscreenParams(mCurrentFullscreenParams);
-        mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams);
+        mOutlineProvider.updateParams(
+                mCurrentFullscreenParams,
+                mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx);
         invalidateOutline();
     }
 
@@ -1194,8 +1226,8 @@
     void updateTaskSize() {
         ViewGroup.LayoutParams params = getLayoutParams();
         if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) {
-            final int thumbnailPadding = (int) getResources().getDimension(
-                    R.dimen.task_thumbnail_top_margin);
+            final int thumbnailPadding =
+                    mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
 
             Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
             int taskWidth = lastComputedTaskSize.width();
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index a412b39..4f27e21 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -16,7 +16,15 @@
 
 package com.android.quickstep;
 
+import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.quickstep.views.RecentsView;
 
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
@@ -31,4 +39,49 @@
                 outerRule(new NavigationModeSwitchRule(mLauncher)).
                 around(super.getRulesInsideActivityMonitor());
     }
+
+    @Override
+    protected void onLauncherActivityClose(Launcher launcher) {
+        RecentsView recentsView = launcher.getOverviewPanel();
+        if (recentsView != null) {
+            recentsView.finishRecentsAnimation(true, null);
+        }
+    }
+
+    @Override
+    protected void checkLauncherState(Launcher launcher, ContainerType expectedContainerType,
+            boolean isResumed, boolean isStarted) {
+        if (!isInLiveTileMode(launcher, expectedContainerType)) {
+            super.checkLauncherState(launcher, expectedContainerType, isResumed, isStarted);
+        } else {
+            assertTrue("[Live Tile] hasBeenResumed() == isStarted(), hasBeenResumed(): "
+                            + isResumed, isResumed != isStarted);
+        }
+    }
+
+    @Override
+    protected void checkLauncherStateInOverview(Launcher launcher,
+            ContainerType expectedContainerType, boolean isStarted, boolean isResumed) {
+        if (!isInLiveTileMode(launcher, expectedContainerType)) {
+            super.checkLauncherStateInOverview(launcher, expectedContainerType, isStarted,
+                    isResumed);
+        } else {
+            assertTrue(
+                    "[Live Tile] Launcher is not started or has been resumed in state: "
+                            + expectedContainerType,
+                    isStarted && !isResumed);
+        }
+    }
+
+    private boolean isInLiveTileMode(Launcher launcher,
+            LauncherInstrumentation.ContainerType expectedContainerType) {
+        if (!LIVE_TILE.get()
+                || expectedContainerType != LauncherInstrumentation.ContainerType.OVERVIEW) {
+            return false;
+        }
+
+        RecentsView recentsView = launcher.getOverviewPanel();
+        return recentsView.getSizeStrategy().isInLiveTileMode()
+                && recentsView.getRunningTaskId() != -1;
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 9732cdc..6e19436 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -100,6 +100,7 @@
             // The test action.
             mLauncher.getBackground().switchToOverview();
         }
+        closeLauncherActivity();
         mLauncher.pressHome();
     }
 }
diff --git a/res/drawable/add_item_dialog_background.xml b/res/drawable/add_item_dialog_background.xml
new file mode 100644
index 0000000..04bde8f
--- /dev/null
+++ b/res/drawable/add_item_dialog_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle" >
+    <solid android:color="?android:attr/colorBackground" />
+    <corners
+        android:topLeftRadius="?android:attr/dialogCornerRadius"
+        android:topRightRadius="?android:attr/dialogCornerRadius" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/add_item_dialog_button_background.xml b/res/drawable/add_item_dialog_button_background.xml
new file mode 100644
index 0000000..1b4591f
--- /dev/null
+++ b/res/drawable/add_item_dialog_button_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<inset
+    android:insetLeft="@dimen/pin_widget_button_inset_horizontal"
+    android:insetRight="@dimen/pin_widget_button_inset_horizontal"
+    android:insetTop="@dimen/pin_widget_button_inset_vertical"
+    android:insetBottom="@dimen/pin_widget_button_inset_vertical"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <ripple
+        android:color="?android:attr/colorControlHighlight">
+        <item>
+            <shape android:tint="?android:attr/colorAccent" android:shape="rectangle">
+                <corners android:radius="18dp" />
+                <solid android:color="#FFFFFF"  />
+                <padding
+                    android:left="@dimen/pin_widget_button_padding_horizontal"
+                    android:top="@dimen/pin_widget_button_padding_vertical"
+                    android:right="@dimen/pin_widget_button_padding_horizontal"
+                    android:bottom="@dimen/pin_widget_button_padding_vertical" />
+            </shape>
+        </item>
+    </ripple>
+</inset>
\ No newline at end of file
diff --git a/res/drawable/gm_edit_24.xml b/res/drawable/gm_edit_24.xml
new file mode 100644
index 0000000..59a0dc2
--- /dev/null
+++ b/res/drawable/gm_edit_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M20.41,4.94l-1.35,-1.35c-0.78,-0.78 -2.05,-0.78 -2.83,0L3,16.82L3,21h4.18L20.41,7.77c0.79,-0.78 0.79,-2.05 0,-2.83zM6.41,19.06L5,19v-1.36l9.82,-9.82 1.41,1.41 -9.82,9.83z"/>
+</vector>
diff --git a/res/drawable/widget_reconfigure_button_frame.xml b/res/drawable/widget_reconfigure_button_frame.xml
new file mode 100644
index 0000000..37d93ad
--- /dev/null
+++ b/res/drawable/widget_reconfigure_button_frame.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:width="@dimen/widget_reconfigure_button_size"
+        android:height="@dimen/widget_reconfigure_button_size">
+        <shape
+            android:shape="rectangle">
+            <solid android:color="?android:attr/colorAccent" />
+            <corners android:radius="@dimen/widget_reconfigure_button_corner_radius" />
+        </shape>
+    </item>
+    <item
+        android:gravity="center"
+        android:padding="@dimen/widget_reconfigure_button_padding"
+        android:drawable="@drawable/gm_edit_24"
+        android:color="?android:attr/colorPrimary" />
+</layer-list>
diff --git a/res/layout/add_item_confirmation_activity.xml b/res/layout/add_item_confirmation_activity.xml
index b1a1efe..d5e7333 100644
--- a/res/layout/add_item_confirmation_activity.xml
+++ b/res/layout/add_item_confirmation_activity.xml
@@ -17,70 +17,51 @@
 */
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/add_item_confirmation"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:padding="24dp"
     android:orientation="vertical">
-    <ScrollView
+
+    <TextView
+        style="@style/TextHeadline"
+        android:id="@+id/widget_appName"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="24sp"
+        android:ellipsize="end"
+        android:fadingEdge="horizontal"
+        android:singleLine="true"
+        android:maxLines="1" />
+
+    <include layout="@layout/widget_cell"
+        android:id="@+id/widget_cell"
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1"
-        android:clipToPadding="false">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical">
-
-            <TextView
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingBottom="20dp"
-                android:paddingLeft="24dp"
-                android:paddingRight="24dp"
-                android:paddingTop="4dp"
-                android:text="@string/add_item_request_drag_hint" />
-
-            <FrameLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:background="?android:attr/colorPrimaryDark"
-                android:theme="?attr/widgetsTheme">
-
-                <com.android.launcher3.widget.WidgetCell
-                    android:id="@+id/widget_cell"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_gravity="center_horizontal"
-                    android:layout_weight="1"
-                    android:background="?android:attr/colorPrimaryDark"
-                    android:focusable="true"
-                    android:gravity="center_horizontal"
-                    android:orientation="vertical" >
-
-                    <include layout="@layout/widget_cell_content"  />
-
-                </com.android.launcher3.widget.WidgetCell>
-            </FrameLayout>
-        </LinearLayout>
-    </ScrollView>
+        android:layout_marginVertical="16dp" />
 
     <LinearLayout
-        style="?android:attr/buttonBarStyle"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="end"
-        android:paddingBottom="4dp"
-        android:paddingEnd="12dp"
-        android:paddingStart="12dp"
-        android:paddingTop="4dp" >
+        android:padding="8dp"
+        android:orientation="horizontal">
         <Button
-            style="?android:attr/buttonBarButtonStyle"
+            style="@style/Widget.DeviceDefault.Button.Rounded.Colored"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:onClick="onCancelClick"
             android:text="@android:string/cancel" />
+
+        <Space
+            android:layout_width="4dp"
+            android:layout_height="wrap_content" />
+
         <Button
-            style="?android:attr/buttonBarButtonStyle"
+            style="@style/Widget.DeviceDefault.Button.Rounded.Colored"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:onClick="onPlaceAutomaticallyClick"
diff --git a/res/layout/all_apps_tabs.xml b/res/layout/all_apps_tabs.xml
index c684881..2accd2d 100644
--- a/res/layout/all_apps_tabs.xml
+++ b/res/layout/all_apps_tabs.xml
@@ -26,6 +26,7 @@
     android:clipChildren="true"
     android:clipToPadding="false"
     android:descendantFocusability="afterDescendants"
+    android:paddingTop="@dimen/all_apps_header_top_padding"
     launcher:pageIndicator="@+id/tabs" >
 
     <include layout="@layout/all_apps_rv_layout" />
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
index 2e476df..53db5ed 100644
--- a/res/layout/app_widget_resize_frame.xml
+++ b/res/layout/app_widget_resize_frame.xml
@@ -26,6 +26,7 @@
 
         <!-- Frame -->
         <ImageView
+            android:id="@+id/widget_resize_frame"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_gravity="center"
@@ -72,5 +73,17 @@
             android:src="@drawable/ic_widget_resize_handle"
             android:tint="?android:attr/colorAccent" />
 
+        <ImageButton
+            android:id="@+id/widget_reconfigure_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:padding="@dimen/widget_reconfigure_button_padding"
+            android:layout_gravity="bottom|end"
+            android:layout_marginBottom="@dimen/widget_reconfigure_button_margin"
+            android:layout_marginEnd="@dimen/widget_reconfigure_button_margin"
+            android:src="@drawable/widget_reconfigure_button_frame"
+            android:background="?android:attr/selectableItemBackground"
+            android:visibility="gone" />
+
     </FrameLayout>
 </com.android.launcher3.AppWidgetResizeFrame>
\ No newline at end of file
diff --git a/res/layout/deep_shortcut.xml b/res/layout/deep_shortcut.xml
index d6b4a37..0d11b50 100644
--- a/res/layout/deep_shortcut.xml
+++ b/res/layout/deep_shortcut.xml
@@ -32,6 +32,8 @@
         android:paddingEnd="@dimen/popup_padding_end"
         android:drawableEnd="@drawable/ic_drag_handle"
         android:drawablePadding="@dimen/deep_shortcut_drawable_padding"
+        android:singleLine="true"
+        android:ellipsize="end"
         android:textSize="14sp"
         android:textColor="?android:attr/textColorPrimary"
         launcher:layoutHorizontal="true"
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index 68251e4..9f45f30 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -31,7 +31,8 @@
         android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
         android:paddingEnd="@dimen/popup_padding_end"
         android:textSize="14sp"
-        android:maxLines="1"
+        android:singleLine="true"
+        android:ellipsize="end"
         android:textColor="?android:attr/textColorPrimary"
         launcher:iconDisplay="shortcut_popup"
         launcher:layoutHorizontal="true"
diff --git a/res/layout/taskbar_view.xml b/res/layout/taskbar_view.xml
new file mode 100644
index 0000000..96ae43d
--- /dev/null
+++ b/res/layout/taskbar_view.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 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.
+-->
+<Space
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="0dp"
+    android:visibility="gone" />
\ No newline at end of file
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index a3d0070..30bd8b1 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -18,8 +18,8 @@
     android:layout_height="wrap_content">
 
     <!-- The image of the widget. This view does not support padding. Any placement adjustment
-         should be done using margins.
-          width & height are set at runtime after scaling the preview image. -->
+         should be done using margins. Width & height are set at runtime after scaling the preview
+         image. -->
     <com.android.launcher3.widget.WidgetImageView
         android:id="@+id/widget_preview"
         android:layout_width="0dp"
@@ -41,16 +41,15 @@
         android:textColor="?android:attr/textColorPrimary"
         android:textSize="@dimen/widget_cell_font_size" />
 
-    <!-- The original dimensions of the widget (can't be the same text as above due to different
-         style. -->
+    <!-- The original dimensions of the widget -->
     <TextView
         android:id="@+id/widget_dims"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
-        android:textColor="?android:attr/textColorTertiary"
+        android:textColor="?android:attr/textColorSecondary"
         android:textSize="@dimen/widget_cell_font_size"
-        android:alpha="0.8" />
+        android:alpha="0.7" />
 
     <TextView
         android:id="@+id/widget_description"
@@ -58,9 +57,10 @@
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
         android:textSize="@dimen/widget_cell_font_size"
-        android:textColor="?android:attr/textColorTertiary"
+        android:textColor="?android:attr/textColorSecondary"
         android:maxLines="2"
         android:ellipsize="end"
-        android:fadingEdge="horizontal" />
+        android:fadingEdge="horizontal"
+        android:alpha="0.7" />
 
 </merge>
\ No newline at end of file
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
index 916ff1b..a9d523a 100644
--- a/res/layout/widgets_bottom_sheet_content.xml
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -29,16 +29,6 @@
         android:textColor="?android:attr/textColorPrimary"
         android:textSize="24sp"/>
 
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_horizontal"
-        android:paddingTop="4dp"
-        android:fontFamily="sans-serif"
-        android:textColor="?android:attr/textColorTertiary"
-        android:textSize="14sp"
-        android:text="@string/long_press_widget_to_add"/>
-
     <ScrollView
         android:id="@+id/widgets_table_scroll_view"
         android:layout_width="match_parent"
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index ed3a042..598041c 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -56,7 +56,8 @@
             android:layout_height="wrap_content"
             android:ellipsize="end"
             android:maxLines="1"
-            android:textColor="?android:attr/textColorTertiary"
+            android:textColor="?android:attr/textColorSecondary"
+            android:alpha="0.7"
             tools:text="m widgets, n shortcuts" />
 
     </LinearLayout>
diff --git a/res/layout/widgets_search_bar.xml b/res/layout/widgets_search_bar.xml
index 1db7462..e3836df 100644
--- a/res/layout/widgets_search_bar.xml
+++ b/res/layout/widgets_search_bar.xml
@@ -32,5 +32,6 @@
         android:src="@drawable/ic_gm_close_24"
         android:background="?android:selectableItemBackground"
         android:layout_gravity="center"
+        android:contentDescription="@string/widgets_full_sheet_cancel_button_description"
         android:visibility="gone"/>
 </com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar>
\ No newline at end of file
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
index 510e1f4..07a5096 100644
--- a/res/values-night/styles.xml
+++ b/res/values-night/styles.xml
@@ -21,6 +21,8 @@
 
     <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
         <item name="widgetsTheme">@style/WidgetContainerTheme.Dark</item>
+        <item name="android:windowBackground">@drawable/add_item_dialog_background</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
 </resources>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4078ef4..fc2b4bc 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -33,7 +33,6 @@
     <attr name="workspaceKeyShadowColor" format="color" />
     <attr name="workspaceStatusBarScrim" format="reference" />
     <attr name="widgetsTheme" format="reference" />
-    <attr name="loadingIconColor" format="color" />
     <attr name="iconOnlyShortcutColor" format="color" />
     <attr name="eduHalfSheetBGColor" format="color" />
 
@@ -44,7 +43,6 @@
     <attr name="folderTextColor" format="color" />
     <attr name="folderHintColor" format="color" />
     <attr name="workProfileOverlayTextColor" format="color" />
-    <attr name="disabledIconAlpha" format="float" />
 
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
@@ -129,11 +127,10 @@
         <!-- numHotseatIcons defaults to numColumns, if not specified -->
         <attr name="numHotseatIcons" format="integer" />
         <attr name="dbFile" format="string" />
-        <!-- numAllAppsColumns defaults to numColumns, if not specified -->
-        <attr name="numAllAppsColumns" format="integer" />
         <attr name="defaultLayoutId" format="reference" />
         <attr name="demoModeLayoutId" format="reference" />
         <attr name="isScalable" format="boolean" />
+        <attr name="devicePaddingId" format="reference" />
 
     </declare-styleable>
 
@@ -170,6 +167,9 @@
         <attr name="allAppsIconSize" format="float" />
         <!-- allAppsIconTextSize defaults to iconTextSize, if not specified -->
         <attr name="allAppsIconTextSize" format="float" />
+
+        <!-- numAllAppsColumns defaults to GridDisplayOption.numColumns, if not specified -->
+        <attr name="numAllAppsColumns" format="integer" />
     </declare-styleable>
 
     <declare-styleable name="CellLayout">
diff --git a/res/values/config.xml b/res/values/config.xml
index 65e2ab3..57f626c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -87,8 +87,6 @@
 
     <!-- Default packages -->
     <string name="wallpaper_picker_package" translatable="false"></string>
-    <string name="calendar_component_name" translatable="false"></string>
-    <string name="clock_component_name" translatable="false"></string>
     <string name="local_colors_extraction_class" translatable="false"></string>
 
     <!-- Accessibility actions -->
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 9d6c936..1fccdf3 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -56,6 +56,12 @@
     <dimen name="resize_frame_background_padding">24dp</dimen>
     <dimen name="resize_frame_margin">22dp</dimen>
 
+    <!-- App widget reconfigure button -->
+    <dimen name="widget_reconfigure_button_corner_radius">14dp</dimen>
+    <dimen name="widget_reconfigure_button_padding">6dp</dimen>
+    <dimen name="widget_reconfigure_button_margin">32dp</dimen>
+    <dimen name="widget_reconfigure_button_size">36dp</dimen>
+
 <!-- Fast scroll -->
     <dimen name="fastscroll_track_min_width">6dp</dimen>
     <dimen name="fastscroll_track_max_width">8dp</dimen>
@@ -138,6 +144,12 @@
     <dimen name="shortcut_preview_padding_right">0dp</dimen>
     <dimen name="shortcut_preview_padding_top">0dp</dimen>
 
+<!-- Pin widget dialog -->
+    <dimen name="pin_widget_button_padding_horizontal">8dp</dimen>
+    <dimen name="pin_widget_button_padding_vertical">4dp</dimen>
+    <dimen name="pin_widget_button_inset_horizontal">4dp</dimen>
+    <dimen name="pin_widget_button_inset_vertical">6dp</dimen>
+
 <!-- Dragging -->
     <!-- Drag padding to add to the bottom of drop targets -->
     <dimen name="drop_target_drag_padding">14dp</dimen>
@@ -185,7 +197,7 @@
 
 <!-- Deep shortcuts -->
     <dimen name="deep_shortcuts_elevation">0dp</dimen>
-    <dimen name="bg_popup_item_width">234dp</dimen>
+    <dimen name="bg_popup_item_width">216dp</dimen>
     <dimen name="bg_popup_item_height">56dp</dimen>
     <dimen name="pre_drag_view_scale">6dp</dimen>
     <!-- an icon with shortcuts must be dragged this far before the container is removed. -->
@@ -277,4 +289,12 @@
 <!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
     <dimen name="taskbar_size">0dp</dimen>
 
+    <!-- Size of the maximum radius for the enforced rounded rectangles. -->
+    <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
+
+<!-- Overview placeholder to compile in Launcer3 without Quickstep -->
+    <dimen name="task_thumbnail_icon_size">0dp</dimen>
+    <dimen name="task_thumbnail_icon_size_grid">0dp</dimen>
+    <dimen name="overview_task_margin">0dp</dimen>
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1eb123b..1371e91 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -81,6 +81,9 @@
     <!-- Search bar text shown in the popup view showing all available widgets installed on the
          device. [CHAR_LIMIT=50] -->
     <string name="widgets_full_sheet_search_bar_hint">Search</string>
+    <!-- Spoken text for screen readers. This text lets a user know that the button is used to clear
+         the text that the user entered in the search box. [CHAR_LIMIT=none] -->
+    <string name="widgets_full_sheet_cancel_button_description">Clear text from search box</string>
     <!-- Text shown when there is no widgets shown in the popup view showing all available widgets
          installed on the device. [CHAR_LIMIT=none] -->
     <string name="no_widgets_available">No widgets available</string>
@@ -269,8 +272,6 @@
     <string name="app_waiting_download_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> waiting to install</string>
 
     <!-- Strings for widgets & more in the popup container/bottom sheet -->
-    <!-- Title for a bottom sheet that shows widgets for a particular app -->
-    <string name="widgets_bottom_sheet_title"><xliff:g id="name" example="Messenger">%1$s</xliff:g> widgets</string>
 
     <!-- Accessibility title for the popup containing a list of widgets. [CHAR_LIMIT=50] -->
     <string name="widgets_list">Widgets list</string>
@@ -379,9 +380,18 @@
     <!--- heading shown when user opens work apps tab while work apps are paused -->
     <string name="work_apps_paused_title">Work profile is paused</string>
     <!--- body shown when user opens work apps tab while work apps are paused -->
-    <string name="work_apps_paused_body">Work apps can\'t send you notifications, use your battery, or access your location</string>
+    <string name="work_apps_paused_body">Work apps can’t send you notifications, use your battery, or access your location</string>
     <!-- content description for paused work apps list -->
-    <string name="work_apps_paused_content_description">Work profile is paused. Work apps can\’t send you notifications, use your battery, or access your location</string>
+    <string name="work_apps_paused_content_description">Work profile is paused. Work apps can’t send you notifications, use your battery, or access your location</string>
+    <!-- string shown in educational banner about work profile -->
+    <string name="work_apps_paused_edu_banner">Work apps are badged and visible to your IT admin</string>
+    <!-- button string shown to dismiss work tab education -->
+    <string name="work_apps_paused_edu_accept">Got it</string>
+
+    <!-- button string shown pause work profile -->
+    <string name="work_apps_pause_btn_text">Pause work apps</string>
+    <!-- button string shown enable work profile -->
+    <string name="work_apps_enable_btn_text">Turn on</string>
 
     <!-- A hint shown in launcher settings develop options filter box -->
     <string name="developer_options_filter_hint">Filter</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index adc2238..a27cdac 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -145,6 +145,8 @@
 
     <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
+        <item name="android:windowBackground">@drawable/add_item_dialog_background</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
     <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
@@ -281,4 +283,8 @@
         <item name="android:colorControlHighlight">#DFE1E5</item>
         <item name="android:colorForeground">@color/all_apps_bg_hand_fill_dark</item>
     </style>
+
+    <style name="Widget.DeviceDefault.Button.Rounded.Colored" parent="@android:style/Widget.DeviceDefault.Button.Colored">
+        <item name="android:background">@drawable/add_item_dialog_button_background</item>
+    </style>
 </resources>
diff --git a/res/xml/size_limits.xml b/res/xml/size_limits_80x104.xml
similarity index 98%
rename from res/xml/size_limits.xml
rename to res/xml/size_limits_80x104.xml
index ba57014..e11bc5e 100644
--- a/res/xml/size_limits.xml
+++ b/res/xml/size_limits_80x104.xml
@@ -38,7 +38,7 @@
         <workspaceBottomPadding
             launcher:a="0.50"
             launcher:b="0"
-            launcher:c="-16dp"/>
+            launcher:c="16dp"/>
         <hotseatBottomPadding
             launcher:a="0.50"
             launcher:b="0"
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
index e1214ff..6b5678c 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java
@@ -82,7 +82,7 @@
         mTestProfile.numColumns = 5;
         mUserHandle = Process.myUserHandle();
         mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
-                mIconCache, null, null);
+                mIconCache, null, null, null);
         mAdapter.registerAdapterDataObserver(mListener);
 
         doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 84a03d5..12a092d 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -105,7 +105,8 @@
                 mWidgetPreviewLoader,
                 mIconCache,
                 /* iconClickListener= */ view -> {},
-                /* iconLongClickListener= */ view -> false);
+                /* iconLongClickListener= */ view -> false,
+                /* searchBarUIHelper= */ null);
         mViewHolderBinder = new WidgetsListHeaderViewHolderBinder(
                 LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
     }
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
index 075c58d..e090341 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java
@@ -105,7 +105,8 @@
                 mWidgetPreviewLoader,
                 mIconCache,
                 /* iconClickListener= */ view -> {},
-                /* iconLongClickListener= */ view -> false);
+                /* iconLongClickListener= */ view -> false,
+                /* searchBarUIHelper= */ null);
         mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder(
                 LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter);
     }
diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 0c6e717..0935d1c 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -111,7 +111,8 @@
                 mWidgetPreviewLoader,
                 mIconCache,
                 /* iconClickListener= */ view -> {},
-                /* iconLongClickListener= */ view -> false);
+                /* iconLongClickListener= */ view -> false,
+                /* searchBarUIHelper= */ null);
         mViewHolderBinder = new WidgetsListTableViewHolderBinder(
                 mContext,
                 LayoutInflater.from(mTestActivity),
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index d894bb4..95cdbdd 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -98,13 +98,6 @@
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_SNACKBAR | TYPE_WIDGET_RESIZE_FRAME | TYPE_LISTENER;
 
-    // When these types of floating views are open, hide the taskbar hotseat and show the real one.
-    public static final int TYPE_REPLACE_TASKBAR_WITH_HOTSEAT = TYPE_FOLDER | TYPE_ACTION_POPUP;
-
-    // Hide the taskbar when these types of floating views are open.
-    public static final int TYPE_HIDE_TASKBAR = TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGETS_FULL_SHEET
-            | TYPE_ON_BOARD_POPUP;
-
     public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
             & ~TYPE_ALL_APPS_EDU;
 
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index d02efb0..5d41bb5 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -14,12 +14,16 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.SizeF;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
 
@@ -71,6 +75,7 @@
     private LauncherAppWidgetHostView mWidgetView;
     private CellLayout mCellLayout;
     private DragLayer mDragLayer;
+    private ImageButton mReconfigureButton;
 
     private Rect mWidgetPadding;
 
@@ -166,6 +171,15 @@
         DragLayer dl = launcher.getDragLayer();
         AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
                 .inflate(R.layout.app_widget_resize_frame, dl, false);
+        if (widget.hasEnforcedCornerRadius()) {
+            float enforcedCornerRadius = widget.getEnforcedCornerRadius();
+            ImageView imageView = frame.findViewById(R.id.widget_resize_frame);
+            Drawable d = imageView.getDrawable();
+            if (d instanceof GradientDrawable) {
+                GradientDrawable gd = (GradientDrawable) d.mutate();
+                gd.setCornerRadius(enforcedCornerRadius);
+            }
+        }
         frame.setupForWidget(widget, cellLayout, dl);
         ((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
 
@@ -199,6 +213,17 @@
             mDragHandles[INDEX_RIGHT].setVisibility(GONE);
         }
 
+        mReconfigureButton = (ImageButton) findViewById(R.id.widget_reconfigure_button);
+        if (info.isReconfigurable()) {
+            mReconfigureButton.setVisibility(VISIBLE);
+            mReconfigureButton.setOnClickListener(view -> mLauncher
+                    .getAppWidgetHost()
+                    .startConfigActivity(
+                            mLauncher,
+                            mWidgetView.getAppWidgetId(),
+                            Launcher.REQUEST_RECONFIGURE_APPWIDGET));
+        }
+
         // When we create the resize frame, we first mark all cells as unoccupied. The appropriate
         // cells (same if not resized, or different) will be marked as occupied when the resize
         // frame is dismissed.
@@ -570,6 +595,13 @@
         return false;
     }
 
+    private boolean isTouchOnReconfigureButton(MotionEvent ev) {
+        int xFrame = (int) ev.getX() - getLeft();
+        int yFrame = (int) ev.getY() - getTop();
+        mReconfigureButton.getHitRect(sTmpRect);
+        return sTmpRect.contains(xFrame, yFrame);
+    }
+
     @Override
     public boolean onControllerTouchEvent(MotionEvent ev) {
         int action = ev.getAction();
@@ -597,6 +629,11 @@
         if (ev.getAction() == MotionEvent.ACTION_DOWN && handleTouchDown(ev)) {
             return true;
         }
+        // Keep the resize frame open but let a click on the reconfigure button fall through to the
+        // button's OnClickListener.
+        if (isTouchOnReconfigureButton(ev)) {
+            return false;
+        }
         close(false);
         return false;
     }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 2b58fb6..d333b49 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
 import static com.android.launcher3.graphics.IconShape.getShape;
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
@@ -57,11 +56,12 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.PlaceHolderIconDrawable;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.icons.DotRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.PlaceHolderIconDrawable;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -92,12 +92,9 @@
     private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
     private static final float HIGHLIGHT_SCALE = 1.16f;
 
-
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
 
-    private static final int ICON_UPDATE_ANIMATION_DURATION = 375;
-
     private float mScaleForReorderBounce = 1f;
 
     protected final Paint mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -335,7 +332,7 @@
 
     @UiThread
     protected void applyIconAndLabel(ItemInfoWithIcon info) {
-        FastBitmapDrawable iconDrawable = newIcon(getContext(), info);
+        FastBitmapDrawable iconDrawable = info.newIcon(getContext());
         mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
 
         setIcon(iconDrawable);
@@ -933,7 +930,7 @@
 
     private void resetIconScale() {
         if (mIcon instanceof FastBitmapDrawable) {
-            ((FastBitmapDrawable) mIcon).setScale(1f);
+            ((FastBitmapDrawable) mIcon).resetScale();
         }
     }
 
diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java
index 4827f36..7c387b1 100644
--- a/src/com/android/launcher3/DevicePaddings.java
+++ b/src/com/android/launcher3/DevicePaddings.java
@@ -52,8 +52,8 @@
 
     ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();
 
-    public DevicePaddings(Context context) {
-        try (XmlResourceParser parser = context.getResources().getXml(R.xml.size_limits)) {
+    public DevicePaddings(Context context, int devicePaddingId) {
+        try (XmlResourceParser parser = context.getResources().getXml(devicePaddingId)) {
             final int depth = parser.getDepth();
             int type;
             while (((type = parser.next()) != XmlPullParser.END_TAG ||
@@ -94,16 +94,27 @@
                             if (workspaceTopPadding == null
                                     || workspaceBottomPadding == null
                                     || hotseatBottomPadding == null) {
-                                throw new RuntimeException("DevicePadding missing padding.");
+                                if (Utilities.IS_DEBUG_DEVICE) {
+                                    throw new RuntimeException("DevicePadding missing padding.");
+                                }
                             }
 
-                            mDevicePaddings.add(new DevicePadding(maxWidthPx, workspaceTopPadding,
-                                    workspaceBottomPadding, hotseatBottomPadding));
+                            DevicePadding dp = new DevicePadding(maxWidthPx, workspaceTopPadding,
+                                    workspaceBottomPadding, hotseatBottomPadding);
+                            if (dp.isValid()) {
+                                mDevicePaddings.add(dp);
+                            } else {
+                                Log.e(TAG, "Invalid device padding found.");
+                                if (Utilities.IS_DEBUG_DEVICE) {
+                                    throw new RuntimeException("DevicePadding is invalid");
+                                }
+                            }
                         }
                     }
                 }
             }
         } catch (IOException | XmlPullParserException e) {
+            Log.e(TAG, "Failure parsing device padding layout.", e);
             throw new RuntimeException(e);
         }
 
@@ -128,6 +139,9 @@
      */
     public static final class DevicePadding {
 
+        // One for each padding since they can each be off by 1 due to rounding errors.
+        private static final int ROUNDING_THRESHOLD_PX = 3;
+
         private final int maxEmptySpacePx;
         private final PaddingFormula workspaceTopPadding;
         private final PaddingFormula workspaceBottomPadding;
@@ -143,6 +157,10 @@
             this.hotseatBottomPadding = hotseatBottomPadding;
         }
 
+        public int getMaxEmptySpacePx() {
+            return maxEmptySpacePx;
+        }
+
         public int getWorkspaceTopPadding(int extraSpacePx) {
             return workspaceTopPadding.calculate(extraSpacePx);
         }
@@ -154,6 +172,22 @@
         public int getHotseatBottomPadding(int extraSpacePx) {
             return hotseatBottomPadding.calculate(extraSpacePx);
         }
+
+        public boolean isValid() {
+            int workspaceTopPadding = getWorkspaceTopPadding(maxEmptySpacePx);
+            int workspaceBottomPadding = getWorkspaceBottomPadding(maxEmptySpacePx);
+            int hotseatBottomPadding = getHotseatBottomPadding(maxEmptySpacePx);
+            int sum = workspaceTopPadding + workspaceBottomPadding + hotseatBottomPadding;
+            int diff = Math.abs(sum - maxEmptySpacePx);
+            if (DEBUG) {
+                Log.d(TAG, "isValid: workspaceTopPadding=" + workspaceTopPadding
+                        + ", workspaceBottomPadding=" + workspaceBottomPadding
+                        + ", hotseatBottomPadding=" + hotseatBottomPadding
+                        + ", sum=" + sum
+                        + ", diff=" + diff);
+            }
+            return diff <= ROUNDING_THRESHOLD_PX;
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index fa19ee6..1ce5f4d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -104,6 +104,7 @@
     private final int mWorkspacePageIndicatorOverlapWorkspace;
 
     // Workspace icons
+    public float iconScale;
     public int iconSizePx;
     public int iconTextSizePx;
     public int iconDrawablePaddingPx;
@@ -151,6 +152,11 @@
     public int allAppsIconDrawablePaddingPx;
     public float allAppsIconTextSizePx;
 
+    // Overview
+    public int overviewTaskMarginPx;
+    public int overviewTaskIconSizePx;
+    public int overviewTaskThumbnailTopMarginPx;
+
     // Widgets
     public final PointF appWidgetScale = new PointF(1.0f, 1.0f);
 
@@ -297,15 +303,29 @@
                 : (hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx
                         + (isScalableGrid ? 0 : hotseatExtraVerticalSize)));
 
+        overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
+        overviewTaskIconSizePx =
+                isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get() ? res.getDimensionPixelSize(
+                        R.dimen.task_thumbnail_icon_size_grid) : res.getDimensionPixelSize(
+                        R.dimen.task_thumbnail_icon_size);
+        overviewTaskThumbnailTopMarginPx = overviewTaskIconSizePx + overviewTaskMarginPx * 2;
+
         // Calculate all of the remaining variables.
         extraSpace = updateAvailableDimensions(res);
         // Now that we have all of the variables calculated, we can tune certain sizes.
-        if (isScalableGrid) {
-            DevicePadding padding = inv.devicePaddings.getDevicePadding(extraSpace);
-            workspaceTopPadding = padding.getWorkspaceTopPadding(extraSpace);
-            workspaceBottomPadding = padding.getWorkspaceBottomPadding(extraSpace);
+        if (isScalableGrid && inv.devicePaddings != null) {
+            // Paddings were created assuming no scaling, so we first unscale the extra space.
+            int unscaledExtraSpace = (int) (extraSpace / iconScale);
+            DevicePadding padding = inv.devicePaddings.getDevicePadding(unscaledExtraSpace);
 
-            extraHotseatBottomPadding = padding.getHotseatBottomPadding(extraSpace);
+            int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
+            int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
+            int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace);
+
+            workspaceTopPadding = Math.round(paddingWorkspaceTop * iconScale);
+            workspaceBottomPadding = Math.round(paddingWorkspaceBottom * iconScale);
+            extraHotseatBottomPadding = Math.round(paddingHotseatBottom * iconScale);
+
             hotseatBarSizePx += extraHotseatBottomPadding;
             hotseatBarBottomPaddingPx += extraHotseatBottomPadding;
         } else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
@@ -478,6 +498,8 @@
      * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
      */
     public void updateIconSize(float scale, Resources res) {
+        iconScale = scale;
+
         // Workspace
         final boolean isVerticalLayout = isVerticalBarLayout();
         float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize;
@@ -878,7 +900,14 @@
         writer.println(prefix + pxToDpStr("workspacePadding.right", workspacePadding.right));
         writer.println(prefix + pxToDpStr("workspacePadding.bottom", workspacePadding.bottom));
 
+        writer.println(prefix + pxToDpStr("scaleToFit", iconScale));
         writer.println(prefix + pxToDpStr("extraSpace", extraSpace));
+
+        if (inv.devicePaddings != null) {
+            int unscaledExtraSpace = (int) (extraSpace / iconScale);
+            writer.println(prefix + pxToDpStr("maxEmptySpace",
+                    inv.devicePaddings.getDevicePadding(unscaledExtraSpace).getMaxEmptySpacePx()));
+        }
         writer.println(prefix + pxToDpStr("workspaceTopPadding", workspaceTopPadding));
         writer.println(prefix + pxToDpStr("workspaceBottomPadding", workspaceBottomPadding));
         writer.println(prefix + pxToDpStr("extraHotseatBottomPadding", extraHotseatBottomPadding));
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
deleted file mode 100644
index b1fe4a2..0000000
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2008 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;
-
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.Property;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.graphics.PlaceHolderIconDrawable;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.util.Themes;
-
-
-public class FastBitmapDrawable extends Drawable {
-
-    private static final float PRESSED_SCALE = 1.1f;
-
-    private static final float DISABLED_DESATURATION = 1f;
-    private static final float DISABLED_BRIGHTNESS = 0.5f;
-
-    public static final int CLICK_FEEDBACK_DURATION = 200;
-
-    private static ColorFilter sDisabledFColorFilter;
-
-    protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
-    protected Bitmap mBitmap;
-    protected final int mIconColor;
-
-    @Nullable private ColorFilter mColorFilter;
-
-    private boolean mIsPressed;
-    private boolean mIsDisabled;
-    private float mDisabledAlpha = 1f;
-
-    // Animator and properties for the fast bitmap drawable's scale
-    private static final Property<FastBitmapDrawable, Float> SCALE
-            = new Property<FastBitmapDrawable, Float>(Float.TYPE, "scale") {
-        @Override
-        public Float get(FastBitmapDrawable fastBitmapDrawable) {
-            return fastBitmapDrawable.mScale;
-        }
-
-        @Override
-        public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
-            fastBitmapDrawable.mScale = value;
-            fastBitmapDrawable.invalidateSelf();
-        }
-    };
-    private ObjectAnimator mScaleAnimation;
-    private float mScale = 1;
-
-    private int mAlpha = 255;
-
-    public FastBitmapDrawable(Bitmap b) {
-        this(b, Color.TRANSPARENT);
-    }
-
-    public FastBitmapDrawable(BitmapInfo info) {
-        this(info.icon, info.color);
-    }
-
-    protected FastBitmapDrawable(Bitmap b, int iconColor) {
-        this(b, iconColor, false);
-    }
-
-    protected FastBitmapDrawable(Bitmap b, int iconColor, boolean isDisabled) {
-        mBitmap = b;
-        mIconColor = iconColor;
-        setFilterBitmap(true);
-        setIsDisabled(isDisabled);
-    }
-
-    @Override
-    public final void draw(Canvas canvas) {
-        if (mScale != 1f) {
-            int count = canvas.save();
-            Rect bounds = getBounds();
-            canvas.scale(mScale, mScale, bounds.exactCenterX(), bounds.exactCenterY());
-            drawInternal(canvas, bounds);
-            canvas.restoreToCount(count);
-        } else {
-            drawInternal(canvas, getBounds());
-        }
-    }
-
-    protected void drawInternal(Canvas canvas, Rect bounds) {
-        canvas.drawBitmap(mBitmap, null, bounds, mPaint);
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter cf) {
-        mColorFilter = cf;
-        updateFilter();
-    }
-
-    @Override
-    public int getOpacity() {
-        return PixelFormat.TRANSLUCENT;
-    }
-
-    @Override
-    public void setAlpha(int alpha) {
-        if (mAlpha != alpha) {
-            mAlpha = alpha;
-            mPaint.setAlpha(alpha);
-            invalidateSelf();
-        }
-    }
-
-    @Override
-    public void setFilterBitmap(boolean filterBitmap) {
-        mPaint.setFilterBitmap(filterBitmap);
-        mPaint.setAntiAlias(filterBitmap);
-    }
-
-    public int getAlpha() {
-        return mAlpha;
-    }
-
-    public void setScale(float scale) {
-        if (mScaleAnimation != null) {
-            mScaleAnimation.cancel();
-            mScaleAnimation = null;
-        }
-        mScale = scale;
-        invalidateSelf();
-    }
-
-    public float getAnimatedScale() {
-        return mScaleAnimation == null ? 1 : mScale;
-    }
-
-    public float getScale() {
-        return mScale;
-    }
-
-    @Override
-    public int getIntrinsicWidth() {
-        return mBitmap.getWidth();
-    }
-
-    @Override
-    public int getIntrinsicHeight() {
-        return mBitmap.getHeight();
-    }
-
-    @Override
-    public int getMinimumWidth() {
-        return getBounds().width();
-    }
-
-    @Override
-    public int getMinimumHeight() {
-        return getBounds().height();
-    }
-
-    @Override
-    public boolean isStateful() {
-        return true;
-    }
-
-    @Override
-    public ColorFilter getColorFilter() {
-        return mPaint.getColorFilter();
-    }
-
-    @Override
-    protected boolean onStateChange(int[] state) {
-        boolean isPressed = false;
-        for (int s : state) {
-            if (s == android.R.attr.state_pressed) {
-                isPressed = true;
-                break;
-            }
-        }
-        if (mIsPressed != isPressed) {
-            mIsPressed = isPressed;
-
-            if (mScaleAnimation != null) {
-                mScaleAnimation.cancel();
-                mScaleAnimation = null;
-            }
-
-            if (mIsPressed) {
-                // Animate when going to pressed state
-                mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, PRESSED_SCALE);
-                mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
-                mScaleAnimation.setInterpolator(ACCEL);
-                mScaleAnimation.start();
-            } else {
-                if (isVisible()) {
-                    mScaleAnimation = ObjectAnimator.ofFloat(this, SCALE, 1f);
-                    mScaleAnimation.setDuration(CLICK_FEEDBACK_DURATION);
-                    mScaleAnimation.setInterpolator(DEACCEL);
-                    mScaleAnimation.start();
-                } else {
-                    mScale = 1f;
-                    invalidateSelf();
-                }
-            }
-            return true;
-        }
-        return false;
-    }
-
-    public void setIsDisabled(boolean isDisabled) {
-        if (mIsDisabled != isDisabled) {
-            mIsDisabled = isDisabled;
-            updateFilter();
-        }
-    }
-
-    protected boolean isDisabled() {
-        return mIsDisabled;
-    }
-
-    private ColorFilter getDisabledColorFilter() {
-        if (sDisabledFColorFilter == null) {
-            ColorMatrix tempBrightnessMatrix = new ColorMatrix();
-            ColorMatrix tempFilterMatrix = new ColorMatrix();
-
-            tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
-            float scale = 1 - DISABLED_BRIGHTNESS;
-            int brightnessI =   (int) (255 * DISABLED_BRIGHTNESS);
-            float[] mat = tempBrightnessMatrix.getArray();
-            mat[0] = scale;
-            mat[6] = scale;
-            mat[12] = scale;
-            mat[4] = brightnessI;
-            mat[9] = brightnessI;
-            mat[14] = brightnessI;
-            mat[18] = mDisabledAlpha;
-            tempFilterMatrix.preConcat(tempBrightnessMatrix);
-            sDisabledFColorFilter = new ColorMatrixColorFilter(tempFilterMatrix);
-        }
-        return sDisabledFColorFilter;
-    }
-
-    /**
-     * Updates the paint to reflect the current brightness and saturation.
-     */
-    protected void updateFilter() {
-        mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : mColorFilter);
-        invalidateSelf();
-    }
-
-    @Override
-    public ConstantState getConstantState() {
-        return new FastBitmapConstantState(mBitmap, mIconColor, mIsDisabled);
-    }
-
-    protected static class FastBitmapConstantState extends ConstantState {
-        protected final Bitmap mBitmap;
-        protected final int mIconColor;
-        protected final boolean mIsDisabled;
-
-        public FastBitmapConstantState(Bitmap bitmap, int color, boolean isDisabled) {
-            mBitmap = bitmap;
-            mIconColor = color;
-            mIsDisabled = isDisabled;
-        }
-
-        @Override
-        public FastBitmapDrawable newDrawable() {
-            return new FastBitmapDrawable(mBitmap, mIconColor, mIsDisabled);
-        }
-
-        @Override
-        public int getChangingConfigurations() {
-            return 0;
-        }
-    }
-
-    /**
-     * Interface to be implemented by custom {@link BitmapInfo} to handle drawable construction
-     */
-    public interface Factory {
-
-        /**
-         * Called to create a new drawable
-         */
-        FastBitmapDrawable newDrawable();
-    }
-
-    /**
-     * Returns a FastBitmapDrawable with the icon.
-     */
-    public static FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) {
-        FastBitmapDrawable drawable = newIcon(context, info.bitmap);
-        drawable.setIsDisabled(info.isDisabled());
-        return drawable;
-    }
-
-    /**
-     * Creates a drawable for the provided BitmapInfo
-     */
-    public static FastBitmapDrawable newIcon(Context context, BitmapInfo info) {
-        final FastBitmapDrawable drawable;
-        if (info instanceof Factory) {
-            drawable = ((Factory) info).newDrawable();
-        } else if (info.isLowRes()) {
-            drawable = new PlaceHolderIconDrawable(info, context);
-        } else {
-            drawable = new FastBitmapDrawable(info);
-        }
-        drawable.mDisabledAlpha = Themes.getFloat(context, R.attr.disabledIconAlpha, 1f);
-        return drawable;
-    }
-}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index af4a843..4049ed6 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -29,8 +29,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.util.MultiValueAlpha;
-
 import java.util.function.Consumer;
 
 /**
@@ -38,10 +36,6 @@
  */
 public class Hotseat extends CellLayout implements Insettable {
 
-    private static final int ALPHA_INDEX_STATE = 0;
-    private static final int ALPHA_INDEX_REPLACE_TASKBAR = 1;
-    private static final int NUM_ALPHA_CHANNELS = 2;
-
     // Ratio of empty space, qsb should take up to appear visually centered.
     public static final float QSB_CENTER_FACTOR = .325f;
 
@@ -52,10 +46,12 @@
     @Nullable
     private Consumer<Boolean> mOnVisibilityAggregatedCallback;
 
-    private final MultiValueAlpha mMultiValueAlpha;
     private final View mQsb;
     private final int mQsbHeight;
 
+    private final View mTaskbarView;
+    private final int mTaskbarViewHeight;
+
     public Hotseat(Context context) {
         this(context, null);
     }
@@ -66,12 +62,15 @@
 
     public Hotseat(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS, MultiValueAlpha.Mode.MAX);
-        mMultiValueAlpha.setUpdateVisibility(true);
 
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
         mQsbHeight = mQsb.getLayoutParams().height;
         addView(mQsb);
+
+        mTaskbarView = LayoutInflater.from(context).inflate(R.layout.taskbar_view, this, false);
+        mTaskbarViewHeight = mTaskbarView.getLayoutParams().height;
+        // We want taskbar in the back so its background applies to Hotseat as well.
+        addView(mTaskbarView, 0);
     }
 
     /**
@@ -193,6 +192,8 @@
         int width = getShortcutsAndWidgets().getMeasuredWidth();
         mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY));
+        mTaskbarView.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mTaskbarViewHeight, MeasureSpec.EXACTLY));
     }
 
     @Override
@@ -212,6 +213,13 @@
                 - (dp.isTaskbarPresent ? dp.taskbarSize : dp.getInsets().bottom);
         int top = bottom - mQsbHeight;
         mQsb.layout(left, top, right, bottom);
+
+        int taskbarWidth = mTaskbarView.getMeasuredWidth();
+        left = (r - l - taskbarWidth) / 2;
+        right = left + taskbarWidth;
+        bottom = b - t;
+        top = bottom - mTaskbarViewHeight;
+        mTaskbarView.layout(left, top, right, bottom);
     }
 
     /**
@@ -221,12 +229,11 @@
         return mWorkspace.getFirstMatch(new CellLayout[] { this }, itemOperator);
     }
 
-    public MultiValueAlpha.AlphaProperty getStateAlpha() {
-        return mMultiValueAlpha.getProperty(ALPHA_INDEX_STATE);
-    }
-
-    public MultiValueAlpha.AlphaProperty getReplaceTaskbarAlpha() {
-        return mMultiValueAlpha.getProperty(ALPHA_INDEX_REPLACE_TASKBAR);
+    /**
+     * Sets the alpha value of just our ShortcutAndWidgetContainer.
+     */
+    public void setIconsAlpha(float alpha) {
+        getShortcutsAndWidgets().setAlpha(alpha);
     }
 
     /**
@@ -235,4 +242,11 @@
     public View getQsb() {
         return mQsb;
     }
+
+    /**
+     * Returns the Taskbar inside hotseat
+     */
+    public View getTaskbarView() {
+        return mTaskbarView;
+    }
 }
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 348d9ee..b0c3bb4 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -132,6 +132,7 @@
      * Do not query directly. see {@link DeviceProfile#isScalableGrid}.
      */
     protected boolean isScalable;
+    public int devicePaddingId;
 
     public String dbFile;
     public int defaultLayoutId;
@@ -140,7 +141,7 @@
     public DeviceProfile landscapeProfile;
     public DeviceProfile portraitProfile;
 
-    public DevicePaddings devicePaddings;
+    @Nullable public DevicePaddings devicePaddings;
 
     public Point defaultWallpaperSize;
     public Rect defaultWidgetPadding;
@@ -165,6 +166,7 @@
         numHotseatIcons = p.numHotseatIcons;
         numAllAppsColumns = p.numAllAppsColumns;
         isScalable = p.isScalable;
+        devicePaddingId = p.devicePaddingId;
         minCellHeight = p.minCellHeight;
         minCellWidth = p.minCellWidth;
         borderSpacing = p.borderSpacing;
@@ -225,13 +227,17 @@
                 .add(myDisplayOption);
         result.iconSize = defaultDisplayOption.iconSize;
         result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
-        result.allAppsIconSize = Math.min(
-                defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize);
+        if (defaultDisplayOption.allAppsIconSize < myDisplayOption.allAppsIconSize) {
+            result.allAppsIconSize = defaultDisplayOption.allAppsIconSize;
+            result.numAllAppsColumns = defaultDisplayOption.numAllAppsColumns;
+        } else {
+            result.allAppsIconSize = myDisplayOption.allAppsIconSize;
+            result.numAllAppsColumns = myDisplayOption.numAllAppsColumns;
+        }
         result.minCellHeight = defaultDisplayOption.minCellHeight;
         result.minCellWidth = defaultDisplayOption.minCellWidth;
         result.borderSpacing = defaultDisplayOption.borderSpacing;
 
-        devicePaddings = new DevicePaddings(context);
         initGrid(context, myInfo, result);
     }
 
@@ -262,7 +268,6 @@
         ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
 
         DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
-        devicePaddings = new DevicePaddings(context);
         initGrid(context, displayInfo, displayOption);
         return displayOption.grid.name;
     }
@@ -278,8 +283,8 @@
         demoModeLayoutId = closestProfile.demoModeLayoutId;
         numFolderRows = closestProfile.numFolderRows;
         numFolderColumns = closestProfile.numFolderColumns;
-        numAllAppsColumns = closestProfile.numAllAppsColumns;
         isScalable = closestProfile.isScalable;
+        devicePaddingId = closestProfile.devicePaddingId;
 
         mExtraAttrs = closestProfile.extraAttrs;
 
@@ -293,6 +298,7 @@
         minCellHeight = displayOption.minCellHeight;
         minCellWidth = displayOption.minCellWidth;
         borderSpacing = displayOption.borderSpacing;
+        numAllAppsColumns = Math.round(displayOption.numAllAppsColumns);
 
         if (Utilities.isGridOptionsEnabled(context)) {
             allAppsIconSize = displayOption.allAppsIconSize;
@@ -302,6 +308,10 @@
             allAppsIconTextSize = iconTextSize;
         }
 
+        if (devicePaddingId != 0) {
+            devicePaddings = new DevicePaddings(context, devicePaddingId);
+        }
+
         // If the partner customization apk contains any grid overrides, apply them
         // Supported overrides: numRows, numColumns, iconSize
         applyPartnerDeviceProfileOverrides(context, displayInfo.metrics);
@@ -609,12 +619,12 @@
         private final int numHotseatIcons;
 
         private final String dbFile;
-        private final int numAllAppsColumns;
 
         private final int defaultLayoutId;
         private final int demoModeLayoutId;
 
         private final boolean isScalable;
+        private final int devicePaddingId;
 
         private final SparseArray<TypedValue> extraAttrs;
 
@@ -636,11 +646,11 @@
                     R.styleable.GridDisplayOption_numFolderRows, numRows);
             numFolderColumns = a.getInt(
                     R.styleable.GridDisplayOption_numFolderColumns, numColumns);
-            numAllAppsColumns = a.getInt(
-                    R.styleable.GridDisplayOption_numAllAppsColumns, numColumns);
 
             isScalable = a.getBoolean(
                     R.styleable.GridDisplayOption_isScalable, false);
+            devicePaddingId = a.getResourceId(
+                    R.styleable.GridDisplayOption_devicePaddingId, 0);
 
             a.recycle();
 
@@ -656,6 +666,7 @@
         private final float minHeightDps;
         private final boolean canBeDefault;
 
+        private float numAllAppsColumns;
         private float minCellHeight;
         private float minCellWidth;
         private float borderSpacing;
@@ -676,6 +687,8 @@
             minHeightDps = a.getFloat(R.styleable.ProfileDisplayOption_minHeightDps, 0);
             canBeDefault = a.getBoolean(
                     R.styleable.ProfileDisplayOption_canBeDefault, false);
+            numAllAppsColumns = a.getInt(R.styleable.ProfileDisplayOption_numAllAppsColumns,
+                    grid.numColumns);
 
             minCellHeight = a.getFloat(R.styleable.ProfileDisplayOption_minCellHeightDps, 0);
             minCellWidth = a.getFloat(R.styleable.ProfileDisplayOption_minCellWidthDps, 0);
@@ -702,12 +715,14 @@
             minWidthDps = 0;
             minHeightDps = 0;
             canBeDefault = false;
+            numAllAppsColumns = 0;
             minCellHeight = 0;
             minCellWidth = 0;
             borderSpacing = 0;
         }
 
         private DisplayOption multiply(float w) {
+            numAllAppsColumns *= w;
             iconSize *= w;
             landscapeIconSize *= w;
             allAppsIconSize *= w;
@@ -720,6 +735,7 @@
         }
 
         private DisplayOption add(DisplayOption p) {
+            numAllAppsColumns += p.numAllAppsColumns;
             iconSize += p.iconSize;
             landscapeIconSize += p.landscapeIconSize;
             allAppsIconSize += p.allAppsIconSize;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 89c0f66..8785fbc 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2763,6 +2763,13 @@
         return new float[] {NO_SCALE, NO_OFFSET};
     }
 
+    /**
+     * @see LauncherState#getTaskbarScale(Launcher)
+     */
+    public float getNormalTaskbarScale() {
+        return 1f;
+    }
+
     public static Launcher getLauncher(Context context) {
         return fromContext(context);
     }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index e9a3495..46bce93 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -176,8 +176,8 @@
         return launcher.getNormalOverviewScaleAndOffset();
     }
 
-    public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
-        return new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
+    public float getTaskbarScale(Launcher launcher) {
+        return launcher.getNormalTaskbarScale();
     }
 
     public float getOverviewFullscreenProgress() {
@@ -185,12 +185,7 @@
     }
 
     public int getVisibleElements(Launcher launcher) {
-        DeviceProfile deviceProfile = launcher.getDeviceProfile();
-        int flags = WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
-        if (!deviceProfile.isTaskbarPresent) {
-            flags |= HOTSEAT_ICONS;
-        }
-        return flags;
+        return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
     }
 
     /**
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 72eff62..76885cc 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -307,7 +307,7 @@
     /**
      * Returns the currently visible pages.
      */
-    protected Iterable<View> getVisiblePages() {
+    public Iterable<View> getVisiblePages() {
         int panelCount = getPanelCount();
         List<View> visiblePages = new ArrayList<>(panelCount);
         for (int i = mCurrentPage; i < mCurrentPage + panelCount; i++) {
@@ -1512,17 +1512,16 @@
         return getDestinationPage(mOrientationHandler.getPrimaryScroll(this));
     }
 
-    protected int getDestinationPage(int scaledScroll) {
-        return getPageNearestToCenterOfScreen(scaledScroll);
+    protected int getDestinationPage(int primaryScroll) {
+        return getPageNearestToCenterOfScreen(primaryScroll);
     }
 
     public int getPageNearestToCenterOfScreen() {
         return getPageNearestToCenterOfScreen(mOrientationHandler.getPrimaryScroll(this));
     }
 
-    private int getPageNearestToCenterOfScreen(int scaledScroll) {
-        int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
-        int screenCenter = scaledScroll + (pageOrientationSize / 2);
+    private int getPageNearestToCenterOfScreen(int primaryScroll) {
+        int screenCenter = getScreenCenter(primaryScroll);
         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
         int minDistanceFromScreenCenterIndex = -1;
         final int childCount = getChildCount();
@@ -1538,18 +1537,26 @@
     }
 
     private int getDisplacementFromScreenCenter(int childIndex, int screenCenter) {
-        int childSize = getChildVisibleSize(childIndex);
+        int childSize = Math.round(getChildVisibleSize(childIndex));
         int halfChildSize = (childSize / 2);
         int childCenter = getChildOffset(childIndex) + halfChildSize;
         return childCenter - screenCenter;
     }
 
     protected int getDisplacementFromScreenCenter(int childIndex) {
-        int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
-        int screenCenter = mOrientationHandler.getPrimaryScroll(this) + (pageOrientationSize / 2);
+        int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+        int screenCenter = getScreenCenter(primaryScroll);
         return getDisplacementFromScreenCenter(childIndex, screenCenter);
     }
 
+    private int getScreenCenter(int primaryScroll) {
+        float primaryScale = mOrientationHandler.getPrimaryScale(this);
+        float primaryPivot =  mOrientationHandler.getPrimaryValue(getPivotX(), getPivotY());
+        int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
+        return Math.round(primaryScroll + (pageOrientationSize / 2f - primaryPivot) / primaryScale
+                + primaryPivot);
+    }
+
     protected void snapToDestination() {
         snapToPage(getDestinationPage(), getPageSnapDuration());
     }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e57844d..8825710 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -69,6 +69,7 @@
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.GridCustomizationsProvider;
 import com.android.launcher3.graphics.TintedDrawableSpan;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.ShortcutCachingLogic;
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 446c873..2f9c96e 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -37,6 +37,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherIcons;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 6a16da9..bd17348 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -83,6 +83,7 @@
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.graphics.WorkspaceDragScrim;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 412754e..16e022c 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -34,7 +34,6 @@
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
 import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
@@ -54,7 +53,6 @@
 import com.android.launcher3.graphics.WorkspaceDragScrim;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DynamicResource;
-import com.android.launcher3.util.MultiValueAlpha;
 import com.android.systemui.plugins.ResourceProvider;
 
 /**
@@ -96,7 +94,6 @@
         ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
         ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(
                 mLauncher);
-        ScaleAndTranslation qsbScaleAndTranslation = state.getQsbScaleAndTranslation(mLauncher);
         mNewScale = scaleAndTranslation.scale;
         PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
         final int childCount = mWorkspace.getChildCount();
@@ -135,8 +132,7 @@
             }
 
             float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
-            propertySetter.setFloat(hotseat.getStateAlpha(), MultiValueAlpha.VALUE,
-                    hotseatIconsAlpha, config.getInterpolator(ANIM_HOTSEAT_FADE, fadeInterpolator));
+            propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
             float workspacePageIndicatorAlpha = (elements & WORKSPACE_PAGE_INDICATOR) != 0 ? 1 : 0;
             propertySetter.setViewAlpha(mLauncher.getWorkspace().getPageIndicator(),
                     workspacePageIndicatorAlpha, fadeInterpolator);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 591de04..bf0a88f 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -583,10 +583,6 @@
         int padding = mHeader.getMaxTranslation();
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].padding.top = padding;
-            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mUsingTabs) {
-                //add extra space between tabs and recycler view
-                mAH[i].padding.top += mLauncher.getDeviceProfile().edgeMarginPx;
-            }
             mAH[i].applyPadding();
         }
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index 14e3b51..3cc9ce6 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -23,8 +23,6 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.PagedView;
-import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.workprofile.PersonalWorkPagedView;
 
 /**
@@ -43,10 +41,6 @@
 
     public AllAppsPagedView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        int topPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0
-                : context.getResources().getDimensionPixelOffset(
-                        R.dimen.all_apps_header_top_padding);
-        setPadding(0, topPadding, 0, 0);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 86f330c..733d867 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -106,8 +106,8 @@
 
     public FloatingHeaderView(@NonNull Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
-        mHeaderTopPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0 :
-                context.getResources().getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
+        mHeaderTopPadding = context.getResources()
+                .getDimensionPixelSize(R.dimen.all_apps_header_top_padding);
     }
 
     @Override
@@ -125,7 +125,6 @@
             }
         }
         mFixedRows = rows.toArray(new FloatingHeaderRow[rows.size()]);
-        setPadding(0, mHeaderTopPadding, 0, 0);
         mAllRows = mFixedRows;
     }
 
@@ -243,9 +242,7 @@
 
     public int getMaxTranslation() {
         if (mMaxTranslation == 0 && mTabsHidden) {
-            int paddingOffset = getResources().getDimensionPixelSize(
-                    R.dimen.all_apps_search_bar_bottom_padding);
-            return FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0 : paddingOffset;
+            return getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_bottom_padding);
         } else if (mMaxTranslation > 0 && mTabsHidden) {
             return mMaxTranslation + getPaddingTop();
         } else {
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 6b9ed09..7980138 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -77,6 +77,9 @@
 
     public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
             new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+    public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL =
+            v -> ACCEL_DEACCEL.getInterpolation(TOUCH_RESPONSE_INTERPOLATOR.getInterpolation(v));
+
 
     /**
      * Inversion of ZOOM_OUT, compounded with an ease-out.
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 96251f0..03c802a 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -85,7 +85,7 @@
             "ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations.");
 
     public static final BooleanFlag ENABLE_QUICKSTEP_LIVE_TILE = getDebugFlag(
-            "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview");
+            "ENABLE_QUICKSTEP_LIVE_TILE", true, "Enable live tile in Quickstep overview");
 
     // Keep as DeviceFlag to allow remote disable in emergency.
     public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
@@ -93,7 +93,7 @@
 
 
     public static final BooleanFlag ENABLE_DEVICE_SEARCH = new DeviceFlag(
-            "ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps");
+            "ENABLE_DEVICE_SEARCH", true, "Allows on device search in all apps");
 
     public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
             "FOLDER_NAME_SUGGEST", true,
@@ -215,6 +215,9 @@
     public static final BooleanFlag ENABLE_SPLIT_SELECT = getDebugFlag(
             "ENABLE_SPLIT_SELECT", false, "Uses new split screen selection overview UI");
 
+    public static final BooleanFlag ENABLE_ENFORCED_ROUNDED_CORNERS = new DeviceFlag(
+            "ENABLE_ENFORCED_ROUNDED_CORNERS", true, "Enforce rounded corners on all App Widgets");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 7bc9865..b7a7366 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -37,11 +37,14 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.DragShadowBuilder;
 import android.view.View.OnLongClickListener;
 import android.view.View.OnTouchListener;
+import android.view.WindowManager;
+import android.widget.TextView;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -127,6 +130,9 @@
         if (savedInstanceState == null) {
             logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_START);
         }
+
+        TextView widgetAppName = findViewById(R.id.widget_appName);
+        widgetAppName.setText(getApplicationInfo().labelRes);
     }
 
     @Override
@@ -326,4 +332,15 @@
                 .withItemInfo((ItemInfo) mWidgetCell.getWidgetView().getTag())
                 .log(command);
     }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        View view = getWindow().getDecorView();
+        WindowManager.LayoutParams layoutParams =
+                (WindowManager.LayoutParams) view.getLayoutParams();
+        layoutParams.gravity = Gravity.BOTTOM;
+        layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
+        getWindowManager().updateViewLayout(view, layoutParams);
+    }
 }
diff --git a/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
index 92ae670..5cd95dc 100644
--- a/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
+++ b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
@@ -17,8 +17,12 @@
 
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
+import android.graphics.Outline;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -28,14 +32,30 @@
 
     private final LauncherAppWidgetHostView mAppWidgetHostView;
     private Paint mPaint = new Paint();
+    private final Path mClipPath;
 
     public AppWidgetHostViewDrawable(LauncherAppWidgetHostView appWidgetHostView) {
         mAppWidgetHostView = appWidgetHostView;
+        Path clipPath = null;
+        if (appWidgetHostView.getClipToOutline()) {
+            Outline outline = new Outline();
+            mAppWidgetHostView.getOutlineProvider().getOutline(mAppWidgetHostView, outline);
+            Rect rect = new Rect();
+            if (outline.getRect(rect)) {
+                float radius = outline.getRadius();
+                clipPath = new Path();
+                clipPath.addRoundRect(new RectF(rect), radius, radius, Path.Direction.CCW);
+            }
+        }
+        mClipPath = clipPath;
     }
 
     @Override
     public void draw(Canvas canvas) {
         int saveCount = canvas.saveLayer(0, 0, getIntrinsicWidth(), getIntrinsicHeight(), mPaint);
+        if (mClipPath != null) {
+            canvas.clipPath(mClipPath);
+        }
         mAppWidgetHostView.draw(canvas);
         canvas.restoreToCount(saveCount);
     }
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 7a6b4f9..419c3f1 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -110,6 +110,8 @@
         mDragController = dragController;
         recreateControllers();
 
+        mOverviewScrim.setup();
+
         mWorkspaceDragScrim = new WorkspaceDragScrim((this));
         mWorkspaceDragScrim.setWorkspace(workspace);
 
@@ -118,8 +120,6 @@
         mRootView = (LauncherRootView) getParent();
         mSysUiScrim = new SysUiScrim(mRootView);
         mRootView.setSysUiScrim(mSysUiScrim);
-
-
     }
 
     @Override
@@ -555,7 +555,6 @@
     public void setInsets(Rect insets) {
         super.setInsets(insets);
         mSysUiScrim.onInsetsChanged(insets, mAllowSysuiScrims);
-        mOverviewScrim.onInsetsChanged(insets);
     }
 
     public WorkspaceDragScrim getWorkspaceDragScrim() {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index e2816f4..0f26ff4 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -44,7 +44,6 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.FirstFrameAnimatorHelper;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
@@ -52,6 +51,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statemanager.StateManager.StateListener;
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index f543e47..29e7c18 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -28,7 +28,6 @@
 import android.os.Build;
 import android.os.Process;
 
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
@@ -78,7 +77,7 @@
         Drawable d = mContext.getSystemService(LauncherApps.class)
                 .getShortcutIconDrawable(mInfo, LauncherAppState.getIDP(mContext).fillResIconDpi);
         if (d == null) {
-            d = new FastBitmapDrawable(cache.getDefaultIcon(Process.myUserHandle()));
+            d = cache.getDefaultIcon(Process.myUserHandle()).newIcon(mContext);
         }
         return d;
     }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 0235dfa..c1f4643 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -483,7 +483,7 @@
                 icon.verifyHighRes();
                 // Set the callback back to the actual icon, in case
                 // it was captured by the FolderIcon
-                Drawable d = icon.getCompoundDrawables()[1];
+                Drawable d = icon.getIcon();
                 if (d != null) {
                     d.setCallback(icon);
                 }
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 9ae7faf..8244f01 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.folder;
 
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
@@ -33,10 +32,10 @@
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
 import android.view.View;
-import android.widget.TextView;
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -113,7 +112,7 @@
     }
 
     Drawable prepareCreateAnimation(final View destView) {
-        Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
+        Drawable animateDrawable = ((BubbleTextView) destView).getIcon();
         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
                 destView.getMeasuredWidth());
         mReferenceDrawable = animateDrawable;
@@ -401,7 +400,7 @@
             drawable.setLevel(item.getProgressLevel());
             p.drawable = drawable;
         } else {
-            p.drawable = newIcon(mContext, item);
+            p.drawable = item.newIcon(mContext);
         }
         p.drawable.setBounds(0, 0, mIconSize, mIconSize);
         p.item = item;
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 9bc5444..0e61b98 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -30,13 +30,13 @@
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
index c0c3e5e..53303db 100644
--- a/src/com/android/launcher3/graphics/OverviewScrim.java
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -18,10 +18,6 @@
 
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.graphics.Rect;
 import android.util.FloatProperty;
 import android.view.View;
 import android.view.ViewGroup;
@@ -55,15 +51,15 @@
 
     public OverviewScrim(View view) {
         super(view);
-        mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
 
         onExtractedColorsChanged(mWallpaperColorInfo);
     }
 
-    public void onInsetsChanged(Rect insets) {
-        mStableScrimmedView = (OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0
-                ? mLauncher.getHotseat()
-                : mLauncher.getOverviewPanel();
+    /**
+     * Initializes once view hierarchy is established.
+     */
+    public void setup() {
+        mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
     }
 
     public void updateCurrentScrimmedView(ViewGroup root) {
diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
deleted file mode 100644
index b6d25c4..0000000
--- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2018 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 static androidx.core.graphics.ColorUtils.compositeColors;
-
-import static com.android.launcher3.graphics.IconShape.getShapePath;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Path;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-import androidx.core.graphics.ColorUtils;
-
-import com.android.launcher3.FastBitmapDrawable;
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.util.Themes;
-
-/**
- * Subclass which draws a placeholder icon when the actual icon is not yet loaded
- */
-public class PlaceHolderIconDrawable extends FastBitmapDrawable {
-
-    // Path in [0, 100] bounds.
-    private final Path mProgressPath;
-
-    public PlaceHolderIconDrawable(BitmapInfo info, Context context) {
-        super(info);
-
-        mProgressPath = getShapePath();
-        mPaint.setColor(compositeColors(
-                Themes.getAttrColor(context, R.attr.loadingIconColor), info.color));
-    }
-
-    @Override
-    protected void drawInternal(Canvas canvas, Rect bounds) {
-        int saveCount = canvas.save();
-        canvas.translate(bounds.left, bounds.top);
-        canvas.scale(bounds.width() / 100f, bounds.height() / 100f);
-        canvas.drawPath(mProgressPath, mPaint);
-        canvas.restoreToCount(saveCount);
-    }
-
-    /** Updates this placeholder to {@code newIcon} with animation. */
-    public void animateIconUpdate(Drawable newIcon) {
-        int placeholderColor = mPaint.getColor();
-        int originalAlpha = Color.alpha(placeholderColor);
-
-        ValueAnimator iconUpdateAnimation = ValueAnimator.ofInt(originalAlpha, 0);
-        iconUpdateAnimation.setDuration(375);
-        iconUpdateAnimation.addUpdateListener(valueAnimator -> {
-            int newAlpha = (int) valueAnimator.getAnimatedValue();
-            int newColor = ColorUtils.setAlphaComponent(placeholderColor, newAlpha);
-
-            newIcon.setColorFilter(new PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_ATOP));
-        });
-        iconUpdateAnimation.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                newIcon.setColorFilter(null);
-            }
-        });
-        iconUpdateAnimation.start();
-    }
-
-}
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index ce824df..ac0ec5f 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -37,8 +37,8 @@
 import android.util.SparseArray;
 import android.view.ContextThemeWrapper;
 
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.util.Themes;
 
diff --git a/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/src/com/android/launcher3/icons/ClockDrawableWrapper.java
deleted file mode 100644
index 1bd252b..0000000
--- a/src/com/android/launcher3/icons/ClockDrawableWrapper.java
+++ /dev/null
@@ -1,328 +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.icons;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.launcher3.FastBitmapDrawable;
-
-import java.util.Calendar;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic
- * clock icons
- */
-@TargetApi(Build.VERSION_CODES.O)
-public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender {
-
-    private static final String TAG = "ClockDrawableWrapper";
-
-    private static final boolean DISABLE_SECONDS = true;
-
-    // Time after which the clock icon should check for an update. The actual invalidate
-    // will only happen in case of any change.
-    public static final long TICK_MS = DISABLE_SECONDS ? TimeUnit.MINUTES.toMillis(1) : 200L;
-
-    private static final String LAUNCHER_PACKAGE = "com.android.launcher3";
-    private static final String ROUND_ICON_METADATA_KEY = LAUNCHER_PACKAGE
-            + ".LEVEL_PER_TICK_ICON_ROUND";
-    private static final String HOUR_INDEX_METADATA_KEY = LAUNCHER_PACKAGE + ".HOUR_LAYER_INDEX";
-    private static final String MINUTE_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
-            + ".MINUTE_LAYER_INDEX";
-    private static final String SECOND_INDEX_METADATA_KEY = LAUNCHER_PACKAGE
-            + ".SECOND_LAYER_INDEX";
-    private static final String DEFAULT_HOUR_METADATA_KEY = LAUNCHER_PACKAGE
-            + ".DEFAULT_HOUR";
-    private static final String DEFAULT_MINUTE_METADATA_KEY = LAUNCHER_PACKAGE
-            + ".DEFAULT_MINUTE";
-    private static final String DEFAULT_SECOND_METADATA_KEY = LAUNCHER_PACKAGE
-            + ".DEFAULT_SECOND";
-
-    /* Number of levels to jump per second for the second hand */
-    private static final int LEVELS_PER_SECOND = 10;
-
-    public static final int INVALID_VALUE = -1;
-
-    private final AnimationInfo mAnimationInfo = new AnimationInfo();
-    private int mTargetSdkVersion;
-
-    public ClockDrawableWrapper(AdaptiveIconDrawable base) {
-        super(base.getBackground(), base.getForeground());
-    }
-
-    /**
-     * Loads and returns the wrapper from the provided package, or returns null
-     * if it is unable to load.
-     */
-    public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) {
-        try {
-            PackageManager pm = context.getPackageManager();
-            ApplicationInfo appInfo =  pm.getApplicationInfo(pkg,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
-            final Bundle metadata = appInfo.metaData;
-            if (metadata == null) {
-                return null;
-            }
-            int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0);
-            if (drawableId == 0) {
-                return null;
-            }
-
-            Drawable drawable = pm.getResourcesForApplication(appInfo).getDrawableForDensity(
-                    drawableId, iconDpi).mutate();
-            if (!(drawable instanceof AdaptiveIconDrawable)) {
-                return null;
-            }
-
-            ClockDrawableWrapper wrapper =
-                    new ClockDrawableWrapper((AdaptiveIconDrawable) drawable);
-            wrapper.mTargetSdkVersion = appInfo.targetSdkVersion;
-            AnimationInfo info = wrapper.mAnimationInfo;
-
-            info.baseDrawableState = drawable.getConstantState();
-
-            info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE);
-            info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE);
-            info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE);
-
-            info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0);
-            info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0);
-            info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0);
-
-            LayerDrawable foreground = (LayerDrawable) wrapper.getForeground();
-            int layerCount = foreground.getNumberOfLayers();
-            if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) {
-                info.hourLayerIndex = INVALID_VALUE;
-            }
-            if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) {
-                info.minuteLayerIndex = INVALID_VALUE;
-            }
-            if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) {
-                info.secondLayerIndex = INVALID_VALUE;
-            } else if (DISABLE_SECONDS) {
-                foreground.setDrawable(info.secondLayerIndex, null);
-                info.secondLayerIndex = INVALID_VALUE;
-            }
-            return wrapper;
-        } catch (Exception e) {
-            Log.d(TAG, "Unable to load clock drawable info", e);
-        }
-        return null;
-    }
-
-    @Override
-    public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) {
-        iconFactory.disableColorExtraction();
-        float [] scale = new float[1];
-        AdaptiveIconDrawable background = new AdaptiveIconDrawable(
-                getBackground().getConstantState().newDrawable(), null);
-        BitmapInfo bitmapInfo = iconFactory.createBadgedIconBitmap(background,
-                Process.myUserHandle(), mTargetSdkVersion, false, scale);
-
-        return new ClockBitmapInfo(bitmap, color, scale[0], mAnimationInfo, bitmapInfo.icon);
-    }
-
-    @Override
-    public void prepareToDrawOnUi() {
-        mAnimationInfo.applyTime(Calendar.getInstance(), (LayerDrawable) getForeground());
-    }
-
-    private static class AnimationInfo {
-
-        public ConstantState baseDrawableState;
-
-        public int hourLayerIndex;
-        public int minuteLayerIndex;
-        public int secondLayerIndex;
-        public int defaultHour;
-        public int defaultMinute;
-        public int defaultSecond;
-
-        boolean applyTime(Calendar time, LayerDrawable foregroundDrawable) {
-            time.setTimeInMillis(System.currentTimeMillis());
-
-            // We need to rotate by the difference from the default time if one is specified.
-            int convertedHour = (time.get(Calendar.HOUR) + (12 - defaultHour)) % 12;
-            int convertedMinute = (time.get(Calendar.MINUTE) + (60 - defaultMinute)) % 60;
-            int convertedSecond = (time.get(Calendar.SECOND) + (60 - defaultSecond)) % 60;
-
-            boolean invalidate = false;
-            if (hourLayerIndex != INVALID_VALUE) {
-                final Drawable hour = foregroundDrawable.getDrawable(hourLayerIndex);
-                if (hour.setLevel(convertedHour * 60 + time.get(Calendar.MINUTE))) {
-                    invalidate = true;
-                }
-            }
-
-            if (minuteLayerIndex != INVALID_VALUE) {
-                final Drawable minute = foregroundDrawable.getDrawable(minuteLayerIndex);
-                if (minute.setLevel(time.get(Calendar.HOUR) * 60 + convertedMinute)) {
-                    invalidate = true;
-                }
-            }
-
-            if (secondLayerIndex != INVALID_VALUE) {
-                final Drawable second = foregroundDrawable.getDrawable(secondLayerIndex);
-                if (second.setLevel(convertedSecond * LEVELS_PER_SECOND)) {
-                    invalidate = true;
-                }
-            }
-
-            return invalidate;
-        }
-    }
-
-    private static class ClockBitmapInfo extends BitmapInfo implements FastBitmapDrawable.Factory {
-
-        public final float scale;
-        public final int offset;
-        public final AnimationInfo animInfo;
-        public final Bitmap mFlattenedBackground;
-
-        ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo,
-                Bitmap background) {
-            super(icon, color);
-            this.scale = scale;
-            this.animInfo = animInfo;
-            this.offset = (int) Math.ceil(ShadowGenerator.BLUR_FACTOR * icon.getWidth());
-            this.mFlattenedBackground = background;
-        }
-
-        @Override
-        public FastBitmapDrawable newDrawable() {
-            return new ClockIconDrawable(this);
-        }
-    }
-
-    private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable {
-
-        private final Calendar mTime = Calendar.getInstance();
-
-        private final ClockBitmapInfo mInfo;
-
-        private final AdaptiveIconDrawable mFullDrawable;
-        private final LayerDrawable mForeground;
-
-        ClockIconDrawable(ClockBitmapInfo clockInfo) {
-            super(clockInfo);
-
-            mInfo = clockInfo;
-
-            mFullDrawable = (AdaptiveIconDrawable) mInfo.animInfo.baseDrawableState.newDrawable();
-            mForeground = (LayerDrawable) mFullDrawable.getForeground();
-        }
-
-        @Override
-        protected void onBoundsChange(Rect bounds) {
-            super.onBoundsChange(bounds);
-            mFullDrawable.setBounds(bounds);
-        }
-
-        @Override
-        public void drawInternal(Canvas canvas, Rect bounds) {
-            if (mInfo == null) {
-                super.drawInternal(canvas, bounds);
-                return;
-            }
-            // draw the background that is already flattened to a bitmap
-            canvas.drawBitmap(mInfo.mFlattenedBackground, null, bounds, mPaint);
-
-            // prepare and draw the foreground
-            mInfo.animInfo.applyTime(mTime, mForeground);
-
-            canvas.scale(mInfo.scale, mInfo.scale,
-                    bounds.exactCenterX() + mInfo.offset, bounds.exactCenterY() + mInfo.offset);
-            canvas.clipPath(mFullDrawable.getIconMask());
-            mForeground.draw(canvas);
-
-            reschedule();
-        }
-
-        @Override
-        protected void updateFilter() {
-            super.updateFilter();
-            mFullDrawable.setColorFilter(mPaint.getColorFilter());
-        }
-
-        @Override
-        public void run() {
-            if (mInfo.animInfo.applyTime(mTime, mForeground)) {
-                invalidateSelf();
-            } else {
-                reschedule();
-            }
-        }
-
-        @Override
-        public boolean setVisible(boolean visible, boolean restart) {
-            boolean result = super.setVisible(visible, restart);
-            if (visible) {
-                reschedule();
-            } else {
-                unscheduleSelf(this);
-            }
-            return result;
-        }
-
-        private void reschedule() {
-            if (!isVisible()) {
-                return;
-            }
-
-            unscheduleSelf(this);
-            final long upTime = SystemClock.uptimeMillis();
-            final long step = TICK_MS; /* tick every 200 ms */
-            scheduleSelf(this, upTime - ((upTime % step)) + step);
-        }
-
-        @Override
-        public ConstantState getConstantState() {
-            return new ClockConstantState(mInfo, isDisabled());
-        }
-
-        private static class ClockConstantState extends FastBitmapConstantState {
-
-            private final ClockBitmapInfo mInfo;
-
-            ClockConstantState(ClockBitmapInfo info, boolean isDisabled) {
-                super(info.icon, info.color, isDisabled);
-                mInfo = info;
-            }
-
-            @Override
-            public FastBitmapDrawable newDrawable() {
-                ClockIconDrawable drawable = new ClockIconDrawable(mInfo);
-                drawable.setIsDisabled(mIsDisabled);
-                return drawable;
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/icons/IconProvider.java b/src/com/android/launcher3/icons/IconProvider.java
deleted file mode 100644
index 1468b27..0000000
--- a/src/com/android/launcher3/icons/IconProvider.java
+++ /dev/null
@@ -1,251 +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.icons;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Process;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.launcher3.R;
-import com.android.launcher3.icons.BitmapInfo.Extender;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SafeCloseable;
-
-import java.util.Calendar;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-
-/**
- * Class to handle icon loading from different packages
- */
-public class IconProvider {
-
-    private static final String TAG = "IconProvider";
-    private static final boolean DEBUG = false;
-
-    private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";
-
-    private static final String SYSTEM_STATE_SEPARATOR = " ";
-
-    // Default value returned if there are problems getting resources.
-    private static final int NO_ID = 0;
-
-    private static final BiFunction<LauncherActivityInfo, Integer, Drawable> LAI_LOADER =
-            LauncherActivityInfo::getIcon;
-
-    private static final BiFunction<ActivityInfo, PackageManager, Drawable> AI_LOADER =
-            ActivityInfo::loadUnbadgedIcon;
-
-
-    private final Context mContext;
-    private final ComponentName mCalendar;
-    private final ComponentName mClock;
-
-    public IconProvider(Context context) {
-        mContext = context;
-        mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
-        mClock = parseComponentOrNull(context, R.string.clock_component_name);
-    }
-
-    /**
-     * Adds any modification to the provided systemState for dynamic icons. This system state
-     * is used by caches to check for icon invalidation.
-     */
-    public String getSystemStateForPackage(String systemState, String packageName) {
-        if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
-            return systemState + SYSTEM_STATE_SEPARATOR + getDay();
-        } else {
-            return systemState;
-        }
-    }
-
-    /**
-     * Loads the icon for the provided LauncherActivityInfo such that it can be drawn directly
-     * on the UI
-     */
-    public Drawable getIconForUI(LauncherActivityInfo info, int iconDpi) {
-        Drawable icon = getIcon(info, iconDpi);
-        if (icon instanceof BitmapInfo.Extender) {
-            ((Extender) icon).prepareToDrawOnUi();
-        }
-        return icon;
-    }
-
-    /**
-     * Loads the icon for the provided LauncherActivityInfo
-     */
-    public Drawable getIcon(LauncherActivityInfo info, int iconDpi) {
-        return getIcon(info.getApplicationInfo().packageName, info.getUser(),
-                info, iconDpi, LAI_LOADER);
-    }
-
-    /**
-     * Loads the icon for the provided activity info
-     */
-    public Drawable getIcon(ActivityInfo info, UserHandle user) {
-        return getIcon(info.applicationInfo.packageName, user, info, mContext.getPackageManager(),
-                AI_LOADER);
-    }
-
-    private <T, P> Drawable getIcon(String packageName, UserHandle user, T obj, P param,
-            BiFunction<T, P, Drawable> loader) {
-        Drawable icon = null;
-        if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
-            icon = loadCalendarDrawable(0);
-        } else if (mClock != null
-                && mClock.getPackageName().equals(packageName)
-                && Process.myUserHandle().equals(user)) {
-            icon = loadClockDrawable(0);
-        }
-        return icon == null ? loader.apply(obj, param) : icon;
-    }
-
-    private Drawable loadCalendarDrawable(int iconDpi) {
-        PackageManager pm = mContext.getPackageManager();
-        try {
-            final Bundle metadata = pm.getActivityInfo(
-                    mCalendar,
-                    PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA)
-                    .metaData;
-            final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
-            final int id = getDynamicIconId(metadata, resources);
-            if (id != NO_ID) {
-                if (DEBUG) Log.d(TAG, "Got icon #" + id);
-                return resources.getDrawableForDensity(id, iconDpi, null /* theme */);
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            if (DEBUG) {
-                Log.d(TAG, "Could not get activityinfo or resources for package: "
-                        + mCalendar.getPackageName());
-            }
-        }
-        return null;
-    }
-
-    private Drawable loadClockDrawable(int iconDpi) {
-        return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
-    }
-
-    protected boolean isClockIcon(ComponentKey key) {
-        return mClock != null && mClock.equals(key.componentName)
-                && Process.myUserHandle().equals(key.user);
-    }
-
-    /**
-     * @param metadata metadata of the default activity of Calendar
-     * @param resources from the Calendar package
-     * @return the resource id for today's Calendar icon; 0 if resources cannot be found.
-     */
-    private int getDynamicIconId(Bundle metadata, Resources resources) {
-        if (metadata == null) {
-            return NO_ID;
-        }
-        String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
-        final int arrayId = metadata.getInt(key, NO_ID);
-        if (arrayId == NO_ID) {
-            return NO_ID;
-        }
-        try {
-            return resources.obtainTypedArray(arrayId).getResourceId(getDay(), NO_ID);
-        } catch (Resources.NotFoundException e) {
-            if (DEBUG) {
-                Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
-            }
-            return NO_ID;
-        }
-    }
-
-    /**
-     * @return Today's day of the month, zero-indexed.
-     */
-    private int getDay() {
-        return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
-    }
-
-
-    /**
-     * Registers a callback to listen for calendar icon changes.
-     * The callback receives the packageName for the calendar icon
-     */
-    public static SafeCloseable registerIconChangeListener(Context context,
-            BiConsumer<String, UserHandle> callback, Handler handler) {
-        ComponentName calendar = parseComponentOrNull(context, R.string.calendar_component_name);
-        ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
-
-        if (calendar == null && clock == null) {
-            return () -> { };
-        }
-
-        BroadcastReceiver receiver = new DateTimeChangeReceiver(callback);
-        final IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
-        if (calendar != null) {
-            filter.addAction(Intent.ACTION_TIME_CHANGED);
-            filter.addAction(Intent.ACTION_DATE_CHANGED);
-        }
-        context.registerReceiver(receiver, filter, null, handler);
-
-        return () -> context.unregisterReceiver(receiver);
-    }
-
-    private static class DateTimeChangeReceiver extends BroadcastReceiver {
-
-        private final BiConsumer<String, UserHandle> mCallback;
-
-        DateTimeChangeReceiver(BiConsumer<String, UserHandle> callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
-                ComponentName clock = parseComponentOrNull(context, R.string.clock_component_name);
-                if (clock != null) {
-                    mCallback.accept(clock.getPackageName(), Process.myUserHandle());
-                }
-            }
-
-            ComponentName calendar =
-                    parseComponentOrNull(context, R.string.calendar_component_name);
-            if (calendar != null) {
-                for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
-                    mCallback.accept(calendar.getPackageName(), user);
-                }
-            }
-
-        }
-    }
-
-    private static ComponentName parseComponentOrNull(Context context, int resId) {
-        String cn = context.getString(resId);
-        return TextUtils.isEmpty(cn) ? null : ComponentName.unflattenFromString(cn);
-
-    }
-}
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index d95e708..76b2ab0 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -23,6 +23,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.util.PackageManagerHelper;
 
@@ -216,4 +217,14 @@
      * @return a copy of this
      */
     public abstract ItemInfoWithIcon clone();
+
+
+    /**
+     * Returns a FastBitmapDrawable with the icon.
+     */
+    public FastBitmapDrawable newIcon(Context context) {
+        FastBitmapDrawable drawable = bitmap.newIcon(context);
+        drawable.setIsDisabled(isDisabled());
+        return drawable;
+    }
 }
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 7780894..6f9f0d7 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -40,6 +40,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -224,6 +225,7 @@
                 .map(recommendedWidget -> allWidgetItems.get(
                         new ComponentKey(recommendedWidget.getTargetComponent(),
                                 recommendedWidget.user)))
+                .filter(Objects::nonNull)
                 .collect(Collectors.toList());
     }
 
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 530aaed..cecbb0d 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -23,12 +23,12 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
 
 /**
  * Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size.
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index fd1d965..eb2c551 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -53,10 +53,4 @@
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         return new ScaleAndTranslation(0.92f, 0, 0);
     }
-
-    @Override
-    public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
-        // Treat the QSB as part of the hotseat so they move together.
-        return getHotseatScaleAndTranslation(launcher);
-    }
 }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index fce8fff..d593013 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -59,10 +59,11 @@
 
         float scale = grid.workspaceSpringLoadShrinkFactor;
         Rect insets = launcher.getDragLayer().getInsets();
+        int insetsBottom = grid.isTaskbarPresent ? grid.taskbarSize : insets.bottom;
 
         float scaledHeight = scale * ws.getNormalChildHeight();
         float shrunkTop = insets.top + grid.dropTargetBarSizePx;
-        float shrunkBottom = ws.getMeasuredHeight() - insets.bottom
+        float shrunkBottom = ws.getMeasuredHeight() - insetsBottom
                 - grid.workspacePadding.bottom
                 - grid.workspaceSpringLoadedBottomSpace;
         float totalShrunkSpace = shrunkBottom - shrunkTop;
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index e4c67ee..cd74390 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -38,7 +38,6 @@
             PLAY_ATOMIC_OVERVIEW_PEEK,
             SKIP_OVERVIEW,
             SKIP_DEPTH_CONTROLLER,
-            SKIP_TASKBAR,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimationFlags {}
@@ -47,7 +46,6 @@
     public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
     public static final int SKIP_OVERVIEW = 1 << 3;
     public static final int SKIP_DEPTH_CONTROLLER = 1 << 4;
-    public static final int SKIP_TASKBAR = 1 << 5;
 
     public long duration;
     public boolean userControlled;
@@ -74,8 +72,6 @@
             ANIM_OVERVIEW_MODAL,
             ANIM_DEPTH,
             ANIM_OVERVIEW_ACTIONS_FADE,
-            ANIM_TASKBAR_FADE,
-            ANIM_HOTSEAT_FADE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimType {}
@@ -95,10 +91,8 @@
     public static final int ANIM_OVERVIEW_MODAL = 13;
     public static final int ANIM_DEPTH = 14;
     public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15;
-    public static final int ANIM_TASKBAR_FADE = 16;
-    public static final int ANIM_HOTSEAT_FADE = 17; // if not set, falls back to ANIM_WORKSPACE_FADE
 
-    private static final int ANIM_TYPES_COUNT = 18;
+    private static final int ANIM_TYPES_COUNT = 16;
 
     protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
 
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index c1cf0c8..19dfe15 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -136,7 +136,7 @@
     }
 
     @Override
-    public int getClearAllScrollOffset(View view, boolean isRtl) {
+    public int getClearAllSidePadding(View view, boolean isRtl) {
         return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2;
     }
 
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index fcfa205..9140a04 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -66,7 +66,7 @@
     float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
     int getMeasuredSize(View view);
     float getPrimarySize(RectF rect);
-    int getClearAllScrollOffset(View view, boolean isRtl);
+    int getClearAllSidePadding(View view, boolean isRtl);
     int getSecondaryDimension(View view);
     FloatProperty<View> getPrimaryViewTranslate();
     FloatProperty<View> getSecondaryViewTranslate();
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 2bc2dc7..29be627 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -132,7 +132,7 @@
     }
 
     @Override
-    public int getClearAllScrollOffset(View view, boolean isRtl) {
+    public int getClearAllSidePadding(View view, boolean isRtl) {
         return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
     }
 
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index 0ea0290..5be9529 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -42,43 +42,16 @@
                 }
             };
 
-    /**
-     * Determines how each alpha should factor into the final alpha.
-     */
-    public enum Mode {
-        BLEND() {
-            @Override
-            public float calculateNewAlpha(float currentAlpha, float otherAlpha) {
-                return currentAlpha * otherAlpha;
-            }
-        },
-
-        MAX() {
-            @Override
-            public float calculateNewAlpha(float currentAlpha, float otherAlpha) {
-                return Math.max(currentAlpha, otherAlpha);
-            }
-        };
-
-        protected abstract float calculateNewAlpha(float currentAlpha, float otherAlpha);
-    }
-
     private final View mView;
     private final AlphaProperty[] mMyProperties;
-    private final Mode mMode;
 
     private int mValidMask;
     // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
     private boolean mUpdateVisibility;
 
     public MultiValueAlpha(View view, int size) {
-        this(view, size, Mode.BLEND);
-    }
-
-    public MultiValueAlpha(View view, int size, Mode mode) {
         mView = view;
         mMyProperties = new AlphaProperty[size];
-        mMode = mode;
 
         mValidMask = 0;
         for (int i = 0; i < size; i++) {
@@ -124,7 +97,7 @@
                 mOthers = 1;
                 for (AlphaProperty prop : mMyProperties) {
                     if (prop != this) {
-                        mOthers = mMode.calculateNewAlpha(mOthers, prop.mValue);
+                        mOthers *= prop.mValue;
                     }
                 }
             }
@@ -134,7 +107,7 @@
             mValidMask = mMyMask;
             mValue = value;
 
-            mView.setAlpha(mMode.calculateNewAlpha(mOthers, mValue));
+            mView.setAlpha(mOthers * mValue);
             if (mUpdateVisibility) {
                 AlphaUpdateListener.updateVisibility(mView);
             }
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 512a286..e8a0635 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -28,6 +28,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 
 /**
@@ -92,10 +93,7 @@
     }
 
     public static int getAttrColor(Context context, int attr) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
-        int colorAccent = ta.getColor(0, 0);
-        ta.recycle();
-        return colorAccent;
+        return GraphicsUtils.getAttrColor(context, attr);
     }
 
     public static boolean getAttrBoolean(Context context, int attr) {
@@ -120,23 +118,6 @@
     }
 
     /**
-     * Returns the alpha corresponding to the theme attribute {@param attr}, in the range [0, 255].
-     */
-    public static int getAlpha(Context context, int attr) {
-        return (int) (255 * getFloat(context, attr, 0) + 0.5f);
-    }
-
-    /**
-     * Returns the alpha corresponding to the theme attribute {@param attr}
-     */
-    public static float getFloat(Context context, int attr, float defValue) {
-        TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
-        float value = ta.getFloat(0, defValue);
-        ta.recycle();
-        return value;
-    }
-
-    /**
      * Scales a color matrix such that, when applied to color R G B A, it produces R' G' B' A' where
      * R' = r * R
      * G' = g * G
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 505c6ce..71aa4ac 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.graphics.Rect;
+import android.view.LayoutInflater;
 import android.view.View.AccessibilityDelegate;
 
 import com.android.launcher3.DeviceProfile;
@@ -67,13 +68,28 @@
     }
 
     /**
+     * Returns a LayoutInflater that is cloned in this Context, so that Views inflated by it will
+     * have the same Context. (i.e. {@link #lookupContext(Context)} will find this ActivityContext.)
+     */
+    default LayoutInflater getLayoutInflater() {
+        if (this instanceof Context) {
+            Context context = (Context) this;
+            return LayoutInflater.from(context).cloneInContext(context);
+        }
+        return null;
+    }
+
+    /**
      * The root view to support drag-and-drop and popup support.
      */
     BaseDragLayer getDragLayer();
 
     DeviceProfile getDeviceProfile();
 
-    static <T extends ActivityContext> T lookupContext(Context context) {
+    /**
+     * Returns the ActivityContext associated with the given Context.
+     */
+    static <T extends Context & ActivityContext> T lookupContext(Context context) {
         if (context instanceof ActivityContext) {
             return (T) context;
         } else if (context instanceof ContextWrapper) {
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 5c18faf..687318f 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
+import android.graphics.Outline;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Handler;
@@ -32,12 +33,14 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.AdapterView;
 import android.widget.Advanceable;
 import android.widget.RemoteViews;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.Launcher;
@@ -95,6 +98,18 @@
     private final Rect mWidgetSizeAtDrag = new Rect();
     private final RectF mTempRectF = new RectF();
     private final boolean mIsRtl;
+    private final Rect mEnforcedRectangle = new Rect();
+    private final float mEnforcedCornerRadius;
+    private final ViewOutlineProvider mCornerRadiusEnforcementOutline = new ViewOutlineProvider() {
+        @Override
+        public void getOutline(View view, Outline outline) {
+            if (mEnforcedRectangle.isEmpty() || mEnforcedCornerRadius <= 0) {
+                outline.setEmpty();
+            } else {
+                outline.setRoundRect(mEnforcedRectangle, mEnforcedCornerRadius);
+            }
+        }
+    };
 
     public LauncherAppWidgetHostView(Context context) {
         super(context);
@@ -112,6 +127,8 @@
         mIsRtl = Utilities.isRtl(context.getResources());
         mColorExtractor = LocalColorExtractor.newInstance(getContext());
         mColorExtractor.setListener(this);
+
+        mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(getContext());
     }
 
     @Override
@@ -169,7 +186,7 @@
         if (viewGroup instanceof AdapterView) {
             return true;
         } else {
-            for (int i=0; i < viewGroup.getChildCount(); i++) {
+            for (int i = 0; i < viewGroup.getChildCount(); i++) {
                 View child = viewGroup.getChildAt(i);
                 if (child instanceof ViewGroup) {
                     if (checkScrollableRecursively((ViewGroup) child)) {
@@ -272,6 +289,8 @@
             int pageId = mWorkspace.getPageIndexForScreenId(info.screenId);
             updateColorExtraction(mCurrentWidgetSize, pageId);
         }
+
+        enforceRoundedCorners();
     }
 
     /** Starts the drag mode. */
@@ -469,4 +488,40 @@
         }
         return false;
     }
+
+    @UiThread
+    private void resetRoundedCorners() {
+        setOutlineProvider(ViewOutlineProvider.BACKGROUND);
+        setClipToOutline(false);
+    }
+
+    @UiThread
+    private void enforceRoundedCorners() {
+        if (mEnforcedCornerRadius <= 0 || !RoundedCornerEnforcement.isRoundedCornerEnabled()) {
+            resetRoundedCorners();
+            return;
+        }
+        View background = RoundedCornerEnforcement.findBackground(this);
+        if (background == null
+                || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+            resetRoundedCorners();
+            return;
+        }
+        RoundedCornerEnforcement.computeRoundedRectangle(this,
+                background,
+                mEnforcedRectangle);
+        setOutlineProvider(mCornerRadiusEnforcementOutline);
+        setClipToOutline(true);
+    }
+
+    /** Returns the corner radius currently enforced, in pixels. */
+    public float getEnforcedCornerRadius() {
+        return mEnforcedCornerRadius;
+    }
+
+    /** Returns true if the corner radius are enforced for this App Widget. */
+    public boolean hasEnforcedCornerRadius() {
+        return getClipToOutline();
+    }
+
 }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index 8689fbf..ad61495 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -139,6 +139,10 @@
         }
     }
 
+    public boolean isReconfigurable() {
+        return configure != null && (getWidgetFeatures() & WIDGET_FEATURE_RECONFIGURABLE) != 0;
+    }
+
     @Override
     public final ComponentName getComponent() {
         return provider;
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 4b113d8..3308eec 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.widget;
 
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 
 import android.content.Context;
@@ -37,8 +36,8 @@
 import android.widget.RemoteViews;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.R;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -152,12 +151,12 @@
             //   2) Preload icon in the center
             //   3) Setup icon in the center and app icon in the top right corner.
             if (mDisabledForSafeMode) {
-                FastBitmapDrawable disabledIcon = newIcon(getContext(), info);
+                FastBitmapDrawable disabledIcon = info.newIcon(getContext());
                 disabledIcon.setIsDisabled(true);
                 mCenterDrawable = disabledIcon;
                 mSettingIconDrawable = null;
             } else if (isReadyForClickSetup()) {
-                mCenterDrawable = newIcon(getContext(), info);
+                mCenterDrawable = info.newIcon(getContext());
                 mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
                 updateSettingColor(info.bitmap.color);
             } else {
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 247a748..ad0a401 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -29,7 +29,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.PendingAddItemInfo;
@@ -38,6 +37,7 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.widget.dragndrop.AppWidgetHostViewDragListener;
 
@@ -54,10 +54,13 @@
 
     @Nullable private RemoteViews mRemoteViewsPreview;
     @Nullable private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
+    private final float mEnforcedRoundedCornersForWidget;
 
     public PendingItemDragHelper(View view) {
         super(view);
         mAddInfo = (PendingAddItemInfo) view.getTag();
+        mEnforcedRoundedCornersForWidget = RoundedCornerEnforcement.computeEnforcedRadius(
+                view.getContext());
     }
 
     /**
@@ -115,10 +118,14 @@
                         .addDragListener(new AppWidgetHostViewDragListener(launcher));
             }
             if (preview == null) {
-                preview = new FastBitmapDrawable(
+                FastBitmapDrawable p = new FastBitmapDrawable(
                         app.getWidgetCache().generateWidgetPreview(launcher,
                                 createWidgetInfo.info, maxWidth, null,
                                 previewSizeBeforeScale).first);
+                if (RoundedCornerEnforcement.isRoundedCornerEnabled()) {
+                    p.setRoundedCornersRadius(mEnforcedRoundedCornersForWidget);
+                }
+                preview = p;
             }
 
             if (previewSizeBeforeScale[0] < previewBitmapWidth) {
diff --git a/src/com/android/launcher3/widget/RoundedCornerEnforcement.java b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
new file mode 100644
index 0000000..1e46ffd
--- /dev/null
+++ b/src/com/android/launcher3/widget/RoundedCornerEnforcement.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2021 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.widget;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities to compute the enforced the use of rounded corners on App Widgets.
+ */
+public class RoundedCornerEnforcement {
+    // This class is only a namespace and not meant to be instantiated.
+    private RoundedCornerEnforcement() {
+    }
+
+    /**
+     * Find the background view for a widget.
+     *
+     * @param appWidget the view containing the App Widget (typically the instance of
+     * {@link AppWidgetHostView}).
+     */
+    @Nullable
+    public static View findBackground(@NonNull View appWidget) {
+        List<View> backgrounds = findViewsWithId(appWidget, android.R.id.background);
+        if (backgrounds.size() == 1) {
+            return backgrounds.get(0);
+        }
+        // Really, the argument should contain the widget, so it cannot be the background.
+        if (appWidget instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) appWidget;
+            if (vg.getChildCount() > 0) {
+                return findUndefinedBackground(vg.getChildAt(0));
+            }
+        }
+        return appWidget;
+    }
+
+    /**
+     * Check whether the app widget has opted out of the enforcement.
+     */
+    public static boolean hasAppWidgetOptedOut(@NonNull View appWidget, @NonNull View background) {
+        return background.getId() == android.R.id.background && background.getClipToOutline();
+    }
+
+    /** Check if the app widget is in the deny list. */
+    public static boolean isRoundedCornerEnabled() {
+        return Utilities.ATLEAST_S && FeatureFlags.ENABLE_ENFORCED_ROUNDED_CORNERS.get();
+    }
+
+    /**
+     * Computes the rounded rectangle needed for this app widget.
+     *
+     * @param appWidget View onto which the rounded rectangle will be applied.
+     * @param background Background view. This must be either {@code appWidget} or a descendant
+     *                  of {@code appWidget}.
+     * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame
+     *                of {@code appWidget}.
+     */
+    public static void computeRoundedRectangle(@NonNull View appWidget, @NonNull View background,
+            @NonNull Rect outRect) {
+        outRect.left = 0;
+        outRect.right = background.getWidth();
+        outRect.top = 0;
+        outRect.bottom = background.getHeight();
+        while (background != appWidget) {
+            outRect.offset(background.getLeft(), background.getTop());
+            background = (View) background.getParent();
+        }
+    }
+
+    /**
+     * Computes the radius of the rounded rectangle that should be applied to a widget expanded
+     * in the given context.
+     */
+    public static float computeEnforcedRadius(@NonNull Context context) {
+        if (!Utilities.ATLEAST_S) {
+            return 0;
+        }
+        Resources res = context.getResources();
+        float systemRadius = res.getDimension(android.R.dimen.system_app_widget_background_radius);
+        float defaultRadius = res.getDimension(R.dimen.enforced_rounded_corner_max_radius);
+        return Math.min(defaultRadius, systemRadius);
+    }
+
+    private static List<View> findViewsWithId(View view, @IdRes int viewId) {
+        List<View> output = new ArrayList<>();
+        accumulateViewsWithId(view, viewId, output);
+        return output;
+    }
+
+    // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
+    private static void accumulateViewsWithId(View view, @IdRes int viewId, List<View> output) {
+        if (view.getId() == viewId) {
+            output.add(view);
+            return;
+        }
+        if (view instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) view;
+            for (int i = 0; i < vg.getChildCount(); i++) {
+                accumulateViewsWithId(vg.getChildAt(i), viewId, output);
+            }
+        }
+    }
+
+    private static boolean isViewVisible(View view) {
+        if (view.getVisibility() != View.VISIBLE) {
+            return false;
+        }
+        return !view.willNotDraw() || view.getForeground() != null || view.getBackground() != null;
+    }
+
+    @Nullable
+    private static View findUndefinedBackground(View current) {
+        if (current.getVisibility() != View.VISIBLE) {
+            return null;
+        }
+        if (isViewVisible(current)) {
+            return current;
+        }
+        View lastVisibleView = null;
+        // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
+        // something, or a ViewGroup that contains more than one view.
+        if (current instanceof ViewGroup) {
+            ViewGroup vg = (ViewGroup) current;
+            for (int i = 0; i < vg.getChildCount(); i++) {
+                View visibleView = findUndefinedBackground(vg.getChildAt(i));
+                if (visibleView != null) {
+                    if (lastVisibleView != null) {
+                        return current; // At least two visible children
+                    }
+                    lastVisibleView = visibleView;
+                }
+            }
+        }
+        return lastVisibleView;
+    }
+}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 1b0e1ce..c11b68e 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -40,12 +40,12 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapRenderer;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.model.WidgetItem;
 
 /**
@@ -94,6 +94,7 @@
     protected final BaseActivity mActivity;
     protected final DeviceProfile mDeviceProfile;
     private final CheckLongPressHelper mLongPressHelper;
+    private final float mEnforcedCornerRadius;
 
     private RemoteViews mPreview;
     private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
@@ -118,6 +119,7 @@
         setWillNotDraw(false);
         setClipToPadding(false);
         setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
+        mEnforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context);
     }
 
     private void setContainerWidth() {
@@ -245,7 +247,9 @@
     }
 
     public void applyPreview(Bitmap bitmap) {
-        applyPreview(new FastBitmapDrawable(bitmap));
+        FastBitmapDrawable drawable = new FastBitmapDrawable(bitmap);
+        drawable.setRoundedCornersRadius(mEnforcedCornerRadius);
+        applyPreview(drawable);
     }
 
     private void applyPreview(Drawable drawable) {
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 267f9f7..bbb0d92 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -26,6 +26,7 @@
 import android.util.Pair;
 import android.view.Gravity;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
@@ -119,8 +120,7 @@
 
     public void populateAndShow(ItemInfo itemInfo) {
         mOriginalItemInfo = itemInfo;
-        ((TextView) findViewById(R.id.title)).setText(getContext().getString(
-                R.string.widgets_bottom_sheet_title, mOriginalItemInfo.title));
+        ((TextView) findViewById(R.id.title)).setText(mOriginalItemInfo.title);
 
         onWidgetsBound();
         attachToContainer();
@@ -153,6 +153,19 @@
         });
     }
 
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mNoIntercept = false;
+            ScrollView scrollView = findViewById(R.id.widgets_table_scroll_view);
+            if (getPopupContainer().isEventOverView(scrollView, ev)
+                    && scrollView.getScrollY() > 0) {
+                mNoIntercept = true;
+            }
+        }
+        return super.onControllerInterceptTouchEvent(ev);
+    }
+
     protected WidgetCell addItemCell(ViewGroup parent) {
         WidgetCell widget = (WidgetCell) LayoutInflater.from(getContext())
                 .inflate(R.layout.widget_cell, parent, false);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index f43f712..29c00b2 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -34,6 +34,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.TextView;
@@ -57,6 +58,7 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.picker.search.SearchModeListener;
 import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
 import com.android.launcher3.workprofile.PersonalWorkPagedView;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
@@ -70,7 +72,8 @@
  */
 public class WidgetsFullSheet extends BaseWidgetSheet
         implements Insettable, ProviderChangedListener, OnActivePageChangedListener,
-        WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener {
+        WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener,
+        WidgetsSearchBarUIHelper {
     private static final String TAG = WidgetsFullSheet.class.getSimpleName();
 
     private static final long DEFAULT_OPEN_DURATION = 267;
@@ -554,6 +557,17 @@
         return super.onBackPressed();
     }
 
+    @Override
+    public void onDragStart(boolean start, float startDisplacement) {
+        super.onDragStart(start, startDisplacement);
+        getWindowInsetsController().hide(WindowInsets.Type.ime());
+    }
+
+    @Override
+    public void clearSearchBarFocus() {
+        mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus();
+    }
+
     /** A holder class for holding adapters & their corresponding recycler view. */
     private final class AdapterHolder {
         static final int PRIMARY = 0;
@@ -576,7 +590,9 @@
                     apps.getWidgetCache(),
                     apps.getIconCache(),
                     /* iconClickListener= */ WidgetsFullSheet.this,
-                    /* iconLongClickListener= */ WidgetsFullSheet.this);
+                    /* iconLongClickListener= */ WidgetsFullSheet.this,
+                    /* WidgetsSearchBarUIHelper= */
+                    mAdapterType == SEARCH ? WidgetsFullSheet.this : null);
             mWidgetsListAdapter.setHasStableIds(true);
             switch (mAdapterType) {
                 case PRIMARY:
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index d841c64..d9c9d4d 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
+import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -69,6 +70,7 @@
     private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header;
     private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header;
 
+    @Nullable private final WidgetsSearchBarUIHelper mSearchBarUIHelper;
     private final WidgetsDiffReporter mDiffReporter;
     private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
     private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
@@ -88,7 +90,9 @@
 
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
             WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache,
-            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
+            OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
+            @Nullable WidgetsSearchBarUIHelper searchBarUIHelper) {
+        mSearchBarUIHelper = searchBarUIHelper;
         mDiffReporter = new WidgetsDiffReporter(iconCache, this);
         mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context,
                 layoutInflater, iconClickListener, iconLongClickListener,
@@ -237,6 +241,9 @@
 
     @Override
     public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) {
+        if (mSearchBarUIHelper != null) {
+            mSearchBarUIHelper.clearSearchBarFocus();
+        }
         if (showWidgets) {
             mWidgetsContentVisiblePackageUserKey = packageUserKey;
             updateVisibleEntries();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 75dd409..497c72e 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static com.android.launcher3.FastBitmapDrawable.newIcon;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -32,11 +30,11 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.graphics.PlaceHolderIconDrawable;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.icons.PlaceHolderIconDrawable;
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.PackageItemInfo;
@@ -145,7 +143,7 @@
     }
 
     private void setIcon(PackageItemInfo info) {
-        FastBitmapDrawable icon = newIcon(getContext(), info);
+        FastBitmapDrawable icon = info.newIcon(getContext());
         applyDrawables(icon);
         mIconDrawable = icon;
         if (mIconDrawable != null) {
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
index cc33619..56a08b1 100644
--- a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -79,4 +79,9 @@
         super.onDetachedFromWindow();
         mController.onDestroy();
     }
+
+    @Override
+    public void clearSearchBarFocus() {
+        mController.clearFocus();
+    }
 }
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
index ef7bf23..3ac82c0 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
@@ -35,6 +35,11 @@
     void reset();
 
     /**
+     * Clears focus from search bar.
+     */
+    void clearSearchBarFocus();
+
+    /**
      * Sets the vertical location, in pixels, of this search bar relative to its top position.
      */
     void setTranslationY(float translationY);
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
index 6011097..d35a75b 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarController.java
@@ -103,8 +103,7 @@
     public void clearSearchResult() {
         mSearchAlgorithm.cancel(/* interruptActiveRequests= */ true);
         mInput.getText().clear();
-        mInput.clearFocus();
-        mInput.hideKeyboard();
+        clearFocus();
         mSearchModeListener.exitSearchMode();
     }
 
@@ -117,18 +116,24 @@
 
     @Override
     public boolean onBackKey() {
-        mInput.clearFocus();
-        mInput.hideKeyboard();
+        clearFocus();
         return true;
     }
 
     @Override
     public boolean onKey(View view, int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
-            mInput.clearFocus();
-            mInput.hideKeyboard();
+            clearFocus();
             return true;
         }
         return false;
     }
+
+    /**
+     * Clears focus from edit text.
+     */
+    public void clearFocus() {
+        mInput.clearFocus();
+        mInput.hideKeyboard();
+    }
 }
diff --git a/src/com/android/launcher3/util/SafeCloseable.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
similarity index 66%
rename from src/com/android/launcher3/util/SafeCloseable.java
rename to src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
index ba8ee04..edfdc65 100644
--- a/src/com/android/launcher3/util/SafeCloseable.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2021 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.
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.util;
+package com.android.launcher3.widget.picker.search;
 
 /**
- * Extension of closeable which does not throw an exception
+ * UI helper for {@link WidgetsSearchBar}.
  */
-public interface SafeCloseable extends AutoCloseable {
-
-    @Override
-    void close();
+public interface WidgetsSearchBarUIHelper {
+    /**
+     * Clears focus from the search bar.
+     */
+    void clearSearchBarFocus();
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 0edfbed..72b1d22 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -503,6 +503,7 @@
         // Destroy Launcher activity.
         executeOnLauncher(launcher -> {
             if (launcher != null) {
+                onLauncherActivityClose(launcher);
                 launcher.finish();
             }
         });
@@ -524,7 +525,7 @@
         return launcher.getAppsView().getActiveRecyclerView().getCurrentScrollY();
     }
 
-    private static void checkLauncherIntegrity(
+    private void checkLauncherIntegrity(
             Launcher launcher, ContainerType expectedContainerType) {
         if (launcher != null) {
             final StateManager<LauncherState> stateManager = launcher.getStateManager();
@@ -535,10 +536,8 @@
                     stableState == stateManager.getState());
 
             final boolean isResumed = launcher.hasBeenResumed();
-            assertTrue("hasBeenResumed() != isStarted(), hasBeenResumed(): " + isResumed,
-                    isResumed == launcher.isStarted());
-            assertTrue("hasBeenResumed() != isUserActive(), hasBeenResumed(): " + isResumed,
-                    isResumed == launcher.isUserActive());
+            final boolean isStarted = launcher.isStarted();
+            checkLauncherState(launcher, expectedContainerType, isResumed, isStarted);
 
             final int ordinal = stableState.ordinal;
 
@@ -561,8 +560,7 @@
                     break;
                 }
                 case OVERVIEW: {
-                    assertTrue(
-                            "Launcher is not resumed in state: " + expectedContainerType,
+                    checkLauncherStateInOverview(launcher, expectedContainerType, isStarted,
                             isResumed);
                     assertTrue(TestProtocol.stateOrdinalToString(ordinal),
                             ordinal == TestProtocol.OVERVIEW_STATE_ORDINAL);
@@ -587,4 +585,20 @@
                             expectedContainerType == ContainerType.FALLBACK_OVERVIEW);
         }
     }
+
+    protected void checkLauncherState(Launcher launcher, ContainerType expectedContainerType,
+            boolean isResumed, boolean isStarted) {
+        assertTrue("hasBeenResumed() != isStarted(), hasBeenResumed(): " + isResumed,
+                isResumed == isStarted);
+        assertTrue("hasBeenResumed() != isUserActive(), hasBeenResumed(): " + isResumed,
+                isResumed == launcher.isUserActive());
+    }
+
+    protected void checkLauncherStateInOverview(Launcher launcher,
+            ContainerType expectedContainerType, boolean isStarted, boolean isResumed) {
+        assertTrue("Launcher is not resumed in state: " + expectedContainerType,
+                isResumed);
+    }
+
+    protected void onLauncherActivityClose(Launcher launcher) { }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7bfe33c..475a4ff 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -52,6 +52,7 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
@@ -867,6 +868,16 @@
         return object;
     }
 
+    @Nullable
+    UiObject2 findObjectInContainer(UiObject2 container, BySelector selector) {
+        try {
+            return container.findObject(selector);
+        } catch (StaleObjectException e) {
+            fail("The container disappeared from screen");
+            return null;
+        }
+    }
+
     @NonNull
     List<UiObject2> getObjectsInContainer(UiObject2 container, String resName) {
         try {
@@ -1059,6 +1070,11 @@
         final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
         final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
 
+        scrollDownByDistance(container, distance);
+    }
+
+    void scrollDownByDistance(UiObject2 container, int distance) {
+        final Rect containerRect = getVisibleBounds(container);
         final int bottomGestureMarginInContainer = getBottomGestureMarginInContainer(container);
         scroll(
                 container,
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index fe4c712..a3f9ade 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -17,8 +17,8 @@
 package com.android.launcher3.tapl;
 
 import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
+import static com.android.launcher3.tapl.LauncherInstrumentation.log;
 
-import android.graphics.Point;
 import android.graphics.Rect;
 
 import androidx.test.uiautomator.By;
@@ -36,6 +36,7 @@
  */
 public final class Widgets extends LauncherInstrumentation.VisibleContainer {
     private static final int FLING_STEPS = 10;
+    private static final int SCROLL_ATTEMPTS = 60;
 
     Widgets(LauncherInstrumentation launcher) {
         super(launcher);
@@ -49,7 +50,7 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "want to fling forward in widgets")) {
-            LauncherInstrumentation.log("Widgets.flingForward enter");
+            log("Widgets.flingForward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
             mLauncher.scroll(
                     widgetsContainer,
@@ -60,7 +61,7 @@
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) {
                 verifyActiveContainer();
             }
-            LauncherInstrumentation.log("Widgets.flingForward exit");
+            log("Widgets.flingForward exit");
         }
     }
 
@@ -71,7 +72,7 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "want to fling backwards in widgets")) {
-            LauncherInstrumentation.log("Widgets.flingBackward enter");
+            log("Widgets.flingBackward enter");
             final UiObject2 widgetsContainer = verifyActiveContainer();
             mLauncher.scroll(
                     widgetsContainer,
@@ -81,7 +82,7 @@
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
                 verifyActiveContainer();
             }
-            LauncherInstrumentation.log("Widgets.flingBackward exit");
+            log("Widgets.flingBackward exit");
         }
     }
 
@@ -100,24 +101,27 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "getting widget " + labelText + " in widgets list")) {
+            final UiObject2 searchBar = findSearchBar();
+            final int searchBarHeight = searchBar.getVisibleBounds().height();
             final UiObject2 fullWidgetsPicker = verifyActiveContainer();
             mLauncher.assertTrue("Widgets container didn't become scrollable",
                     fullWidgetsPicker.wait(Until.scrollable(true), WAIT_TIME_MS));
-            final Point displaySize = mLauncher.getRealDisplaySize();
 
-            Rect headerRect = new Rect();
-            final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer(headerRect);
+            final UiObject2 widgetsContainer = findTestAppWidgetsTableContainer();
             mLauncher.assertTrue("Can't locate widgets list for the test app: "
                             + mLauncher.getLauncherPackageName(),
                     widgetsContainer != null);
             final BySelector labelSelector = By.clazz("android.widget.TextView").text(labelText);
+            final BySelector previewSelector = By.res(mLauncher.getLauncherPackageName(),
+                    "widget_preview");
             int i = 0;
             for (; ; ) {
                 final Collection<UiObject2> tableRows = widgetsContainer.getChildren();
                 for (UiObject2 row : tableRows) {
                     final Collection<UiObject2> widgetCells = row.getChildren();
                     for (UiObject2 widget : widgetCells) {
-                        final UiObject2 label = widget.findObject(labelSelector);
+                        final UiObject2 label = mLauncher.findObjectInContainer(widget,
+                                labelSelector);
                         if (label == null) {
                             continue;
                         }
@@ -125,19 +129,15 @@
                                 "View is not WidgetCell",
                                 "com.android.launcher3.widget.WidgetCell",
                                 widget.getClassName());
-
-                        return new Widget(mLauncher, widget);
+                        UiObject2 preview = mLauncher.waitForObjectInContainer(widget,
+                                previewSelector);
+                        return new Widget(mLauncher, preview);
                     }
                 }
 
-                mLauncher.assertTrue("Too many attempts", ++i <= 40);
+                mLauncher.assertTrue("Too many attempts", ++i <= SCROLL_ATTEMPTS);
                 final int scroll = getWidgetsScroll();
-                mLauncher.scroll(
-                        fullWidgetsPicker,
-                        Direction.DOWN,
-                        headerRect,
-                        10,
-                        true);
+                mLauncher.scrollDownByDistance(fullWidgetsPicker, searchBarHeight);
                 final int newScroll = getWidgetsScroll();
                 mLauncher.assertTrue(
                         "Scrolled in a wrong direction in Widgets: from " + scroll + " to "
@@ -147,8 +147,20 @@
         }
     }
 
+    private UiObject2 findSearchBar() {
+        final BySelector searchBarContainerSelector = By.res(mLauncher.getLauncherPackageName(),
+                "search_and_recommendations_container");
+        final BySelector searchBarSelector = By.res(mLauncher.getLauncherPackageName(),
+                "widgets_search_bar");
+        final UiObject2 searchBarContainer = mLauncher.waitForLauncherObject(
+                searchBarContainerSelector);
+        UiObject2 searchBar = mLauncher.waitForObjectInContainer(searchBarContainer,
+                searchBarSelector);
+        return searchBar;
+    }
+
     /** Finds the widgets list of this test app from the collapsed full widgets picker. */
-    private UiObject2 findTestAppWidgetsTableContainer(Rect outHeaderRect) {
+    private UiObject2 findTestAppWidgetsTableContainer() {
         final BySelector headerSelector = By.res(mLauncher.getLauncherPackageName(),
                 "widgets_list_header");
         final BySelector targetAppSelector = By.clazz("android.widget.TextView").text(
@@ -157,42 +169,34 @@
                 "widgets_table");
 
         boolean hasHeaderExpanded = false;
-        for (int i = 0; i < 40; i++) {
+        for (int i = 0; i < SCROLL_ATTEMPTS; i++) {
             UiObject2 fullWidgetsPicker = verifyActiveContainer();
 
-            UiObject2 header = fullWidgetsPicker.findObject(headerSelector);
-            outHeaderRect.set(0, 0, 0, header.getVisibleBounds().height());
-            mLauncher.assertTrue("Can't find a widget header", header != null);
+            UiObject2 header = mLauncher.waitForObjectInContainer(fullWidgetsPicker,
+                    headerSelector);
+            int headerHeight = header.getVisibleBounds().height();
 
             // Look for a header that has the test app name.
-            UiObject2 headerTitle = fullWidgetsPicker.findObject(targetAppSelector);
+            UiObject2 headerTitle = mLauncher.findObjectInContainer(fullWidgetsPicker,
+                    targetAppSelector);
             if (headerTitle != null) {
                 // If we find the header and it has not been expanded, let's click it to see the
                 // widgets list.
                 if (!hasHeaderExpanded) {
+                    log("Header has not been expanded. Click to expand.");
                     hasHeaderExpanded = true;
                     mLauncher.clickLauncherObject(headerTitle);
-                    // After clicking the header, the recyclerview has been updated. Let's refresh
-                    // the container UIObject2.
-                    fullWidgetsPicker = verifyActiveContainer();
-                    // Refresh headerTitle because the first instance is stale after
-                    // verifyActiveContainer call.
-                    headerTitle = fullWidgetsPicker.findObject(targetAppSelector);
                 }
 
                 // Look for a widgets list.
-                UiObject2 widgetsContainer = fullWidgetsPicker.findObject(widgetsContainerSelector);
+                UiObject2 widgetsContainer = mLauncher.findObjectInContainer(fullWidgetsPicker,
+                        widgetsContainerSelector);
                 if (widgetsContainer != null) {
+                    log("Widgets container found.");
                     return widgetsContainer;
                 }
-
             }
-            mLauncher.scroll(
-                    fullWidgetsPicker,
-                    Direction.DOWN,
-                    outHeaderRect,
-                    /* steps= */ 10,
-                    /* slowDown= */ true);
+            mLauncher.scrollDownByDistance(fullWidgetsPicker, headerHeight);
         }
 
         return null;