Merge "Import translations. DO NOT MERGE ANYWHERE" into tm-qpr-dev
diff --git a/res/drawable/ic_desktop.xml b/quickstep/res/drawable/ic_desktop.xml
similarity index 100%
rename from res/drawable/ic_desktop.xml
rename to quickstep/res/drawable/ic_desktop.xml
diff --git a/quickstep/res/drawable/ic_empty_desktop.xml b/quickstep/res/drawable/ic_empty_desktop.xml
new file mode 100644
index 0000000..cbf1856
--- /dev/null
+++ b/quickstep/res/drawable/ic_empty_desktop.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2023 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="92dp"
+    android:height="80dp"
+    android:tint="?android:attr/textColorPrimary"
+    android:viewportHeight="80.0"
+    android:viewportWidth="92.0">
+    <path
+        android:fillColor="#AAFFFFFF"
+        android:pathData="M 14.365954,80 Q 10.981668,80 8.4908345,77.509166 6,75.018332 6,71.634046 V 36.193807 q 0,-3.384286 2.4908345,-5.87512 2.4908335,-2.493091 5.8751195,-2.493091 H 22.35738 V 8.365954 q 0,-3.3842855 2.538217,-5.8751198 Q 27.433811,0 30.723337,0 h 46.910711 q 3.479041,0 5.969878,2.4908342 2.490834,2.4908343 2.490834,5.8751198 v 35.442495 q 0,3.384286 -2.490834,5.87512 -2.490837,2.490835 -5.969878,2.490835 h -7.896671 v 19.459642 q 0,3.384286 -2.49083,5.87512 Q 64.755713,80 61.371423,80 Z m 0,-8.365954 h 47.005469 q 0,0 0,0 0,0 0,0 V 43.526426 H 14.365954 v 28.10762 q 0,0 0,0 0,0 0,0 z M 69.737377,43.808449 h 7.896671 q 0,0 0,0 0,0 0,0 V 15.698573 H 30.723337 v 12.127023 h 30.740592 q 3.479048,0 5.877376,2.445711 2.396072,2.443454 2.396072,5.82774 z" />
+</vector>
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 2ec9d4c..f454835 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -32,6 +32,19 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
+    <TextView
+        android:id="@+id/empty_view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_marginTop="@dimen/overview_task_margin"
+        android:drawablePadding="@dimen/recents_empty_message_text_padding"
+        android:text="@string/recents_empty_desktop_message"
+        android:textColor="?android:textColorPrimary"
+        android:textSize="@dimen/recents_empty_message_text_size"
+        android:drawableTop="@drawable/ic_empty_desktop"
+        android:drawableTint="?android:attr/textColorPrimary" />
+
     <!--
          TODO(b249371338): DesktopTaskView extends from TaskView. TaskView expects TaskThumbnailView
          and IconView with these ids to be present. Need to refactor RecentsView to accept child
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 01d92d1..2b5975d 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -30,6 +30,9 @@
     <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
     <string name="recents_empty_message">No recent items</string>
 
+    <!-- Recents: The empty recents desktop tile string. [CHAR LIMIT=NONE] -->
+    <string name="recents_empty_desktop_message">No desktop items</string>
+
     <!-- Content description for the recent apps's accessibility option that opens its usage settings. [CHAR LIMIT=NONE] -->
     <string name="accessibility_app_usage_settings">App usage settings</string>
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 94d62b2..745defc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -425,7 +425,7 @@
 
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
-        return (mViewCallbacks != null && mViewCallbacks.onKeyUp(keyCode, event))
+        return (mViewCallbacks != null && mViewCallbacks.onKeyUp(keyCode, event, mIsRtl))
                 || super.onKeyUp(keyCode, event);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index f0f361e..c1f764f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -19,6 +19,7 @@
 
 import android.animation.Animator;
 import android.view.KeyEvent;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -125,18 +126,26 @@
     }
 
     private int launchTaskAt(int index) {
-        KeyboardQuickSwitchTaskView taskView = mKeyboardQuickSwitchView.getTaskAt(index);
+        if (mCloseAnimation != null) {
+            // Ignore taps on task views and alt key unpresses while the close animation is running.
+            return -1;
+        }
+        // Even with a valid index, this can be null if the user tries to quick switch before the
+        // views have been added in the KeyboardQuickSwitchView.
+        View taskView = mKeyboardQuickSwitchView.getTaskAt(index);
         GroupTask task = mControllerCallbacks.getTaskAt(index);
-        if (taskView == null || task == null) {
+        if (task == null) {
             return Math.max(0, index);
         } else if (task.task2 == null) {
             UI_HELPER_EXECUTOR.execute(() ->
                     ActivityManagerWrapper.getInstance().startActivityFromRecents(
                             task.task1.key,
                             mControllers.taskbarActivityContext.getActivityLaunchOptions(
-                                    taskView, null).options));
+                                    taskView == null ? mKeyboardQuickSwitchView : taskView, null)
+                                    .options));
         } else {
-            mControllers.uiController.launchSplitTasks(taskView, task);
+            mControllers.uiController.launchSplitTasks(
+                    taskView == null ? mKeyboardQuickSwitchView : taskView, task);
         }
         return -1;
     }
@@ -160,15 +169,26 @@
 
     class ViewCallbacks {
 
-        boolean onKeyUp(int keyCode, KeyEvent event) {
-            if (keyCode != KeyEvent.KEYCODE_TAB) {
+        boolean onKeyUp(int keyCode, KeyEvent event, boolean isRTL) {
+            if (keyCode != KeyEvent.KEYCODE_TAB
+                    && keyCode != KeyEvent.KEYCODE_DPAD_RIGHT
+                    && keyCode != KeyEvent.KEYCODE_DPAD_LEFT
+                    && keyCode != KeyEvent.KEYCODE_GRAVE
+                    && keyCode != KeyEvent.KEYCODE_ESCAPE) {
                 return false;
             }
+            if (keyCode == KeyEvent.KEYCODE_GRAVE || keyCode == KeyEvent.KEYCODE_ESCAPE) {
+                closeQuickSwitchView(true);
+                return true;
+            }
+            boolean traverseBackwards = (keyCode == KeyEvent.KEYCODE_TAB && event.isShiftPressed())
+                    || (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && !isRTL)
+                    || (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && isRTL);
             int taskCount = mControllerCallbacks.getTaskCount();
             int toIndex = mCurrentFocusIndex == -1
                     // Focus the second-most recent app if possible
                     ? (taskCount > 1 ? 1 : 0)
-                    : (event.isShiftPressed()
+                    : (traverseBackwards
                             // focus a more recent task or loop back to the opposite end
                             ? Math.max(0, mCurrentFocusIndex == 0
                                     ? taskCount - 1 : mCurrentFocusIndex - 1)
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index dcef6d3..3046076 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -388,8 +388,7 @@
     }
 
     @Override
-    public void launchSplitTasks(View taskView, GroupTask groupTask) {
-        super.launchSplitTasks(taskView, groupTask);
+    public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
         mLauncher.launchSplitTasks(taskView, groupTask);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 9b0f8c4..ab52adb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -560,8 +560,6 @@
     public void updateSysuiStateFlags(int systemUiStateFlags, boolean fromInit) {
         mControllers.navbarButtonsViewController.updateStateForSysuiFlags(systemUiStateFlags,
                 fromInit);
-        mControllers.taskbarViewController.setImeIsVisible(
-                mControllers.navbarButtonsViewController.isImeVisible());
         int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
                 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
         onNotificationShadeExpandChanged((systemUiStateFlags & shadeExpandedFlags) != 0, fromInit);
@@ -879,9 +877,9 @@
      * (potentially breaking a split pair).
      */
     private void launchFromTaskbarPreservingSplitIfVisible(RecentsView recents, ItemInfo info) {
-        recents.findLastActiveTaskAndRunCallback(
+        recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
                 info.getTargetComponent(),
-                (Consumer<Task>) foundTask -> {
+                foundTask -> {
                     if (foundTask != null) {
                         TaskView foundTaskView =
                                 recents.getTaskViewByTaskId(foundTask.key.id);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index 2d20582..37d9090 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -20,6 +20,7 @@
 import android.graphics.Color
 import android.graphics.Paint
 import android.graphics.Path
+import android.graphics.RectF
 import com.android.launcher3.R
 import com.android.launcher3.Utilities.mapRange
 import com.android.launcher3.Utilities.mapToRange
@@ -30,7 +31,8 @@
 /** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
 class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
 
-    val paint: Paint = Paint()
+    val paint = Paint()
+    val lastDrawnTransientRect = RectF()
     var backgroundHeight = context.deviceProfile.taskbarSize.toFloat()
     var translationYForSwipe = 0f
 
@@ -55,6 +57,9 @@
     private val stashedHandleWidth =
         context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width)
 
+    private val stashedHandleHeight =
+        context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_height)
+
     init {
         paint.color = context.getColor(R.color.taskbar_background)
         paint.flags = Paint.ANTI_ALIAS_FLAG
@@ -102,8 +107,8 @@
     /** Draws the background with the given paint and height, on the provided canvas. */
     fun draw(canvas: Canvas) {
         canvas.save()
-        canvas.translate(0f, canvas.height - backgroundHeight - bottomMargin)
         if (!isTransientTaskbar || transientBackgroundBounds.isEmpty) {
+            canvas.translate(0f, canvas.height - backgroundHeight - bottomMargin)
             // Draw the background behind taskbar content.
             canvas.drawRect(0f, 0f, canvas.width.toFloat(), backgroundHeight, paint)
 
@@ -114,12 +119,25 @@
             canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
             canvas.drawPath(invertedRightCornerPath, paint)
         } else {
-            // Approximates the stash/unstash animation to transform the background.
+            // backgroundHeight is a value from [0...maxBackgroundHeight], so we can use it as a
+            // proxy to figure out the animation progress of the stash/unstash animation.
             val progress = backgroundHeight / maxBackgroundHeight
+
+            // At progress 0, we draw the background as the stashed handle.
+            // At progress 1, we draw the background as the full taskbar.
+            val newBackgroundHeight =
+                mapRange(progress, stashedHandleHeight.toFloat(), maxBackgroundHeight)
             val fullWidth = transientBackgroundBounds.width()
             val newWidth = mapRange(progress, stashedHandleWidth.toFloat(), fullWidth.toFloat())
-            val delta = fullWidth - newWidth
-            canvas.translate(0f, bottomMargin * ((1f - progress) / 2f))
+            val halfWidthDelta = (fullWidth - newWidth) / 2f
+            val radius = newBackgroundHeight / 2f
+            val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f)
+
+            // Aligns the bottom with the bottom of the stashed handle.
+            val bottom =
+                canvas.height - bottomMargin +
+                    bottomMarginProgress +
+                    (-mapRange(1f - progress, 0f, stashedHandleHeight / 2f) + translationYForSwipe)
 
             // Draw shadow.
             val shadowAlpha =
@@ -131,20 +149,15 @@
                 setColorAlphaBound(Color.BLACK, Math.round(shadowAlpha))
             )
 
-            // Draw background.
-            val radius = backgroundHeight / 2f
-
-            canvas.drawRoundRect(
-                transientBackgroundBounds.left + (delta / 2f),
-                translationYForSwipe,
-                transientBackgroundBounds.right - (delta / 2f),
-                backgroundHeight + translationYForSwipe,
-                radius,
-                radius,
-                paint
+            lastDrawnTransientRect.set(
+                transientBackgroundBounds.left + halfWidthDelta,
+                bottom - newBackgroundHeight,
+                transientBackgroundBounds.right - halfWidthDelta,
+                bottom
             )
-        }
 
+            canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
+        }
         canvas.restore()
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index d1fea7b..4e79011 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -33,6 +33,7 @@
 import android.graphics.Canvas;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.util.Pair;
@@ -69,11 +70,13 @@
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.quickstep.util.LogUtils;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.draganddrop.DragAndDropConstants;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -310,9 +313,6 @@
         if (mDisallowGlobalDrag) {
             AbstractFloatingView.closeAllOpenViewsExcept(mActivity, TYPE_TASKBAR_ALL_APPS);
         } else {
-            // stash the transient taskbar
-            mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
-
             AbstractFloatingView.closeAllOpenViews(mActivity);
         }
 
@@ -340,7 +340,7 @@
                 if (DEBUG_DRAG_SHADOW_SURFACE) {
                     canvas.drawColor(0xffff0000);
                 }
-                float scale = mDragObject.dragView.getScaleX();
+                float scale = mDragObject.dragView.getEndScale();
                 canvas.scale(scale, scale);
                 mDragObject.dragView.draw(canvas);
                 canvas.restore();
@@ -395,6 +395,15 @@
             com.android.launcher3.logging.InstanceId launcherInstanceId = instanceIds.second;
 
             intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);
+            if (DisplayController.isTransientTaskbar(mActivity)) {
+                // Tell WM Shell to ignore drag events in the provided transient taskbar region.
+                TaskbarDragLayer dragLayer = mControllers.taskbarActivityContext.getDragLayer();
+                int[] locationOnScreen = dragLayer.getLocationOnScreen();
+                RectF disallowExternalDropRegion = new RectF(dragLayer.getLastDrawnTransientRect());
+                disallowExternalDropRegion.offset(locationOnScreen[0], locationOnScreen[1]);
+                intent.putExtra(DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION,
+                        disallowExternalDropRegion);
+            }
 
             ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
             if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
@@ -421,9 +430,6 @@
                     if (dragEvent.getResult()) {
                         maybeOnDragEnd();
                     } else {
-                        // un-stash the transient taskbar in case drag and drop was canceled
-                        mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
-
                         // This will take care of calling maybeOnDragEnd() after the animation
                         animateGlobalDragViewToOriginalPosition(btv, dragEvent);
                     }
@@ -451,6 +457,9 @@
             mControllers.taskbarAutohideSuspendController.updateFlag(
                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
             mActivity.onDragEnd();
+            // Note, this must be done last to ensure no AutohideSuspendFlags are active, as that
+            // will prevent us from stashing until the timeout.
+            mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
         }
     }
 
@@ -592,7 +601,15 @@
         View target = findTaskbarTargetForIconView(originalView);
 
         int[] toPosition = target.getLocationOnScreen();
-        float toScale = (float) target.getWidth() / mDragIconSize;
+        float iconSize = target.getWidth();
+        if (target instanceof BubbleTextView) {
+            Rect bounds = new Rect();
+            ((BubbleTextView) target).getSourceVisualDragBounds(bounds);
+            toPosition[0] += bounds.left;
+            toPosition[1] += bounds.top;
+            iconSize = bounds.width();
+        }
+        float toScale = iconSize / mDragIconSize;
         float toAlpha = (target == originalView) ? 1f : 0f;
         MultiValueUpdateListener listener = new MultiValueUpdateListener() {
             final FloatProp mDx = new FloatProp(fromX, toPosition[0], 0,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 7114849..58d6244 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -183,6 +184,11 @@
         invalidate();
     }
 
+    /** Returns the bounds in DragLayer coordinates of where the transient background was drawn. */
+    protected RectF getLastDrawnTransientRect() {
+        return mBackgroundRenderer.getLastDrawnTransientRect();
+    }
+
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 1c6aca7..0b86155 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.window.RefreshRateTracker;
 import com.android.quickstep.RecentsAnimationCallbacks;
@@ -119,11 +118,10 @@
                     mLauncherState = finalState;
                     updateStateForFlag(FLAG_TRANSITION_STATE_RUNNING, false);
                     applyState();
-                    boolean finalStateOverview = finalState instanceof OverviewState;
                     boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
                     com.android.launcher3.taskbar.Utilities.setOverviewDragState(
-                            mControllers, finalStateOverview /*disallowGlobalDrag*/,
-                            disallowLongClick, finalStateOverview /*allowInitialSplitSelection*/);
+                            mControllers, finalState.disallowTaskbarGlobalDrag(),
+                            disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
                 }
             };
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 10d339b..3479255 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -25,6 +25,7 @@
 import android.view.View;
 
 import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.model.data.ItemInfo;
@@ -188,9 +189,9 @@
         if (recentsView == null) {
             return;
         }
-        recentsView.findLastActiveTaskAndRunCallback(
+        recentsView.getSplitSelectController().findLastActiveTaskAndRunCallback(
                 splitSelectSource.intent.getComponent(),
-                (Consumer<Task>) foundTask -> {
+                foundTask -> {
                     splitSelectSource.alreadyRunningTaskId = foundTask == null
                             ? INVALID_TASK_ID
                             : foundTask.key.id;
@@ -205,9 +206,9 @@
      */
     public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
         RecentsView recents = getRecentsView();
-        recents.findLastActiveTaskAndRunCallback(
+        recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
                 info.getTargetComponent(),
-                (Consumer<Task>) foundTask -> {
+                foundTask -> {
                     if (foundTask != null) {
                         TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
                         // TODO (b/266482558): This additional null check is needed because there
@@ -253,15 +254,6 @@
     }
 
     /**
-     * Closes the Keyboard Quick Switch View.
-     *
-     * No-op if the view is already closed
-     */
-    public void closeQuickSwitchView() {
-        mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
-    }
-
-    /**
      * Launches the focused task and closes the Keyboard Quick Switch View.
      *
      * If the overlay or view are closed, or the overview task is focused, then Overview is
@@ -280,5 +272,5 @@
      *
      * No-op if the view is not yet open.
      */
-    public void launchSplitTasks(View taskview, GroupTask groupTask) { }
+    public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 483f711..c1e85aa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -76,9 +76,6 @@
     private View.OnClickListener mIconClickListener;
     private View.OnLongClickListener mIconLongClickListener;
 
-    // Prevents dispatching touches to children if true
-    private boolean mTouchEnabled = true;
-
     // Only non-null when the corresponding Folder is open.
     private @Nullable FolderIcon mLeaveBehindFolderIcon;
 
@@ -391,14 +388,6 @@
     }
 
     @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (!mTouchEnabled) {
-            return true;
-        }
-        return super.dispatchTouchEvent(ev);
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         mControllerCallbacks.onInterceptTouchEvent(ev);
         return super.onInterceptTouchEvent(ev);
@@ -406,9 +395,6 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (!mTouchEnabled) {
-            return true;
-        }
         if (mIconLayoutBounds.left <= event.getX()
                 && event.getX() <= mIconLayoutBounds.right
                 && !DisplayController.isTransientTaskbar(mActivityContext)) {
@@ -429,11 +415,6 @@
         return super.onTouchEvent(event);
     }
 
-    public void setTouchesEnabled(boolean touchEnabled) {
-        this.mTouchEnabled = touchEnabled;
-        mControllerCallbacks.clearTouchInProgress();
-    }
-
     /**
      * Returns whether the given MotionEvent, *in screen coorindates*, is within any Taskbar item's
      * touch bounds.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index a25ca8c..c708838 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -200,14 +200,6 @@
     }
 
     /**
-     * Should be called when the IME visibility changes, so we can make Taskbar not steal touches.
-     */
-    public void setImeIsVisible(boolean isImeVisible) {
-        mTaskbarView.setTouchesEnabled(!isImeVisible
-                || DisplayController.isTransientTaskbar(mActivity));
-    }
-
-    /**
      * Should be called when the recents button is disabled, so we can hide taskbar icons as well.
      */
     public void setRecentsButtonDisabled(boolean isDisabled) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 80ce369..3dbe6c8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -579,9 +579,9 @@
         RecentsView recentsView = getOverviewPanel();
         // Check if there is already an instance of this app running, if so, initiate the split
         // using that.
-        recentsView.findLastActiveTaskAndRunCallback(
+        mSplitSelectStateController.findLastActiveTaskAndRunCallback(
                 splitSelectSource.intent.getComponent(),
-                (Consumer<Task>) foundTask -> {
+                foundTask -> {
                     splitSelectSource.alreadyRunningTaskId = foundTask == null
                             ? INVALID_TASK_ID
                             : foundTask.key.id;
@@ -595,8 +595,7 @@
     }
 
     /** TODO(b/266482558) Migrate into SplitSelectStateController or someplace split specific. */
-    private void startSplitToHome(
-            SplitSelectSource source) {
+    private void startSplitToHome(SplitSelectSource source) {
         AbstractFloatingView.closeAllOpenViews(this);
         int splitPlaceholderSize = getResources().getDimensionPixelSize(
                 R.dimen.split_placeholder_size);
@@ -604,14 +603,14 @@
                 R.dimen.split_placeholder_inset);
         Rect tempRect = new Rect();
 
-        SplitSelectStateController controller = getSplitSelectStateController();
-        controller.setInitialTaskSelect(source.intent, source.position.stagePosition,
-                source.itemInfo, source.splitEvent, source.alreadyRunningTaskId);
+        mSplitSelectStateController.setInitialTaskSelect(source.intent,
+                source.position.stagePosition, source.itemInfo, source.splitEvent,
+                source.alreadyRunningTaskId);
 
         RecentsView recentsView = getOverviewPanel();
         recentsView.getPagedOrientationHandler().getInitialSplitPlaceholderBounds(
                 splitPlaceholderSize, splitPlaceholderInset, getDeviceProfile(),
-                controller.getActiveSplitStagePosition(), tempRect);
+                mSplitSelectStateController.getActiveSplitStagePosition(), tempRect);
 
         PendingAnimation anim = new PendingAnimation(TABLET_HOME_TO_SPLIT.getDuration());
         RectF startingTaskRect = new RectF();
@@ -620,12 +619,12 @@
         floatingTaskView.setAlpha(1);
         floatingTaskView.addStagingAnimation(anim, startingTaskRect, tempRect,
                 false /* fadeWithThumbnail */, true /* isStagedTask */);
-        controller.setFirstFloatingTaskView(floatingTaskView);
+        mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationCancel(Animator animation) {
                 getDragLayer().removeView(floatingTaskView);
-                controller.resetState();
+                mSplitSelectStateController.resetState();
             }
         });
         anim.buildAnim().start();
@@ -1156,14 +1155,12 @@
         // Launcher to first restore into Overview state, wait for the relevant tasks and icons to
         // load in, and then proceed to OverviewSplitSelect.
         if (isInState(OVERVIEW_SPLIT_SELECT)) {
-            SplitSelectStateController splitSelectStateController =
-                    ((RecentsView) getOverviewPanel()).getSplitSelectController();
             // Launcher will restart in Overview and then transition to OverviewSplitSelect.
             outState.putIBinder(PENDING_SPLIT_SELECT_INFO, ObjectWrapper.wrap(
                     new PendingSplitSelectInfo(
-                            splitSelectStateController.getInitialTaskId(),
-                            splitSelectStateController.getActiveSplitStagePosition(),
-                            splitSelectStateController.getSplitEvent())
+                            mSplitSelectStateController.getInitialTaskId(),
+                            mSplitSelectStateController.getActiveSplitStagePosition(),
+                            mSplitSelectStateController.getSplitEvent())
             ));
             outState.putInt(RUNTIME_STATE, OVERVIEW.ordinal);
         }
@@ -1214,7 +1211,7 @@
      *
      * If the second split task is missing, launches the first task normally.
      */
-    public void launchSplitTasks(View taskView, GroupTask groupTask) {
+    public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
         if (groupTask.task2 == null) {
             UI_HELPER_EXECUTOR.execute(() ->
                     ActivityManagerWrapper.getInstance().startActivityFromRecents(
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 95eb128..8125fc8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -116,6 +116,18 @@
         return super.isTaskbarAlignedWithHotseat(launcher);
     }
 
+    @Override
+    public boolean disallowTaskbarGlobalDrag() {
+        // Enable global drag in overview
+        return false;
+    }
+
+    @Override
+    public boolean allowTaskbarInitialSplitSelection() {
+        // Disallow split select from taskbar items in overview
+        return false;
+    }
+
     public static float[] getOverviewScaleAndOffsetForBackgroundState(
             BaseDraggingActivity activity) {
         return new float[] {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 0c49e5f..b9221ee 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -18,12 +18,12 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 
 import android.content.Context;
-import android.graphics.Point;
 import android.graphics.Rect;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -70,13 +70,22 @@
         }
     }
 
-    public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
-        Point taskSize = activity.<RecentsView>getOverviewPanel().getSelectedTaskSize();
-        Rect modalTaskSize = new Rect();
-        activity.<RecentsView>getOverviewPanel().getModalTaskSize(modalTaskSize);
+    @Override
+    public boolean isTaskbarStashed(Launcher launcher) {
+        if (FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+            return true;
+        }
+        return super.isTaskbarStashed(launcher);
+    }
 
-        float scale = Math.min((float) modalTaskSize.height() / taskSize.y,
-                (float) modalTaskSize.width() / taskSize.x);
+    public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) {
+        RecentsView recentsView = activity.<RecentsView>getOverviewPanel();
+        Rect taskSize = recentsView.getSelectedTaskBounds();
+        Rect modalTaskSize = new Rect();
+        recentsView.getModalTaskSize(modalTaskSize);
+
+        float scale = Math.min((float) modalTaskSize.height() / taskSize.height(),
+                (float) modalTaskSize.width() / taskSize.width());
 
         return new float[] {scale, NO_OFFSET};
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d075750..233ed46 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -124,6 +124,18 @@
     }
 
     @Override
+    public boolean disallowTaskbarGlobalDrag() {
+        // Disable global drag in overview
+        return true;
+    }
+
+    @Override
+    public boolean allowTaskbarInitialSplitSelection() {
+        // Allow split select from taskbar items in overview
+        return true;
+    }
+
+    @Override
     public String getDescription(Launcher launcher) {
         return launcher.getString(R.string.accessibility_recent_apps);
     }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index f9ad749..998439e 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -52,6 +52,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.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.BaseState;
@@ -339,12 +340,21 @@
      */
     public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
         calculateTaskSize(context, dp, outRect);
-        float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
+        boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
+        int claimedSpaceBelow = isGridOnlyOverview
+                ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarSize
+                : (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
+        int minimumHorizontalPadding = 0;
+        if (!isGridOnlyOverview) {
+            float maxScale = context.getResources().getFloat(R.dimen.overview_modal_max_scale);
+            minimumHorizontalPadding =
+                    Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2);
+        }
         calculateTaskSizeInternal(
                 context, dp,
                 dp.overviewTaskMarginPx,
-                dp.heightPx - outRect.bottom - dp.getInsets().bottom,
-                Math.round((dp.availableWidthPx - outRect.width() * maxScale) / 2),
+                claimedSpaceBelow,
+                minimumHorizontalPadding,
                 1f /*maxScale*/,
                 Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM,
                 outRect);
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 0dcd723..72330ef 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -80,9 +80,9 @@
         boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
         boolean isTablet = activity.getDeviceProfile().isTablet;
 
+        boolean isGridOnlyOverview = isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get();
         // Add overview actions to the menu when in in-place rotate landscape mode.
-        if ((!canLauncherRotate && isInLandscape)
-                || (isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get())) {
+        if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
             // Add screenshot action to task menu.
             List<SystemShortcut> screenshotShortcuts = TaskShortcutFactory.SCREENSHOT
                     .getShortcuts(activity, taskContainer);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index db6d56b..4b1dd43 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.fallback;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.HOME;
@@ -200,8 +202,9 @@
     }
 
     @Override
-    public void setModalStateEnabled(boolean isModalState, boolean animate) {
-        if (isModalState) {
+    public void setModalStateEnabled(int taskId, boolean animate) {
+        if (taskId != INVALID_TASK_ID) {
+            setSelectedTask(taskId);
             mActivity.getStateManager().goToState(RecentsState.MODAL_TASK, animate);
         } else {
             if (mActivity.isInState(RecentsState.MODAL_TASK)) {
diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
index 543ca89..2a8bfa2 100644
--- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
@@ -100,22 +100,34 @@
         mMoveFromCenterAnimation.registerViewForAnimation(view);
     }
 
-    protected void disableClipping(ViewGroup view) {
+    /**
+     * Sets clipToPadding for the view which then could be restored to the original value
+     * using {@link BaseUnfoldMoveFromCenterAnimator#restoreClippings} method call
+     * @param view view to set the property
+     * @param clipToPadding value of the property
+     */
+    protected void setClipToPadding(ViewGroup view, boolean clipToPadding) {
         mOriginalClipToPadding.put(view, view.getClipToPadding());
-        mOriginalClipChildren.put(view, view.getClipChildren());
-        view.setClipToPadding(false);
-        view.setClipChildren(false);
+        view.setClipToPadding(clipToPadding);
     }
 
-    protected void restoreClipping(ViewGroup view) {
-        final Boolean originalClipToPadding = mOriginalClipToPadding.get(view);
-        if (originalClipToPadding != null) {
-            view.setClipToPadding(originalClipToPadding);
-        }
-        final Boolean originalClipChildren = mOriginalClipChildren.get(view);
-        if (originalClipChildren != null) {
-            view.setClipChildren(originalClipChildren);
-        }
+    /**
+     * Sets clipChildren for the view which then could be restored to the original value
+     * using {@link BaseUnfoldMoveFromCenterAnimator#restoreClippings} method call
+     * @param view view to set the property
+     * @param clipChildren value of the property
+     */
+    protected void setClipChildren(ViewGroup view, boolean clipChildren) {
+        mOriginalClipChildren.put(view, view.getClipChildren());
+        view.setClipChildren(clipChildren);
+    }
+
+    /**
+     * Restores original clip properties after their modifications
+     */
+    protected void restoreClippings() {
+        mOriginalClipToPadding.forEach(ViewGroup::setClipToPadding);
+        mOriginalClipChildren.forEach(ViewGroup::setClipChildren);
     }
 
     private class UnfoldMoveFromCenterRotationListener implements
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 1b2bfc9..51afb5b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -29,6 +29,7 @@
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -59,6 +60,7 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.TaskViewUtils;
@@ -79,10 +81,12 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private final RecentsModel mRecentTasksModel;
     private StatsLogManager mStatsLogManager;
     private final SystemUiProxy mSystemUiProxy;
     private final StateManager mStateManager;
-    private final DepthController mDepthController;
+    @Nullable
+    private DepthController mDepthController;
     private @StagePosition int mStagePosition;
     private ItemInfo mItemInfo;
     private Intent mInitialTaskIntent;
@@ -110,6 +114,7 @@
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(mContext);
         mStateManager = stateManager;
         mDepthController = depthController;
+        mRecentTasksModel = RecentsModel.INSTANCE.get(context);
     }
 
     /**
@@ -149,6 +154,50 @@
     }
 
     /**
+     * Pulls the list of active Tasks from RecentsModel, and finds the most recently active Task
+     * matching a given ComponentName. Then uses that Task (which could be null) with the given
+     * callback.
+     *
+     * Used in various task-switching or splitscreen operations when we need to check if there is a
+     * currently running Task of a certain type and use the most recent one.
+     */
+    public void findLastActiveTaskAndRunCallback(ComponentName componentName,
+            Consumer<Task> callback) {
+        mRecentTasksModel.getTasks(taskGroups -> {
+            Task lastActiveTask = null;
+            // Loop through tasks in reverse, since they are ordered with most-recent tasks last.
+            for (int i = taskGroups.size() - 1; i >= 0; i--) {
+                GroupTask groupTask = taskGroups.get(i);
+                Task task1 = groupTask.task1;
+                if (isInstanceOfComponent(task1, componentName)) {
+                    lastActiveTask = task1;
+                    break;
+                }
+                Task task2 = groupTask.task2;
+                if (isInstanceOfComponent(task2, componentName)) {
+                    lastActiveTask = task2;
+                    break;
+                }
+            }
+
+            callback.accept(lastActiveTask);
+        });
+    }
+
+    /**
+     * Checks if a given Task is the most recently-active Task of type componentName. Used for
+     * selecting already-running Tasks for splitscreen.
+     */
+    public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) {
+        // Exclude the task that is already staged
+        if (task == null || task.key.id == mInitialTaskId) {
+            return false;
+        }
+
+        return task.key.baseIntent.getComponent().equals(componentName);
+    }
+
+    /**
      * To be called when the actual tasks ({@link #mInitialTaskId}, {@link #mSecondTaskId}) are
      * to be launched. Call after launcher side animations are complete.
      */
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
index 01a997a..70a12d6 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterHotseatAnimator.java
@@ -41,7 +41,8 @@
         Hotseat hotseat = mLauncher.getHotseat();
 
         ViewGroup hotseatIcons = hotseat.getShortcutsAndWidgets();
-        disableClipping(hotseat);
+        setClipChildren(hotseat, false);
+        setClipToPadding(hotseat, false);
 
         for (int i = 0; i < hotseatIcons.getChildCount(); i++) {
             View child = hotseatIcons.getChildAt(i);
@@ -51,7 +52,7 @@
 
     @Override
     public void onTransitionFinished() {
-        restoreClipping(mLauncher.getHotseat());
+        restoreClippings();
         super.onTransitionFinished();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
index 95a4b8f..7da103e 100644
--- a/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/UnfoldMoveFromCenterWorkspaceAnimator.java
@@ -47,7 +47,8 @@
                     final CellLayout cellLayout = (CellLayout) page;
                     ShortcutAndWidgetContainer itemsContainer = cellLayout
                             .getShortcutsAndWidgets();
-                    disableClipping(cellLayout);
+                    setClipChildren(cellLayout, false);
+                    setClipToPadding(cellLayout, false);
 
                     for (int i = 0; i < itemsContainer.getChildCount(); i++) {
                         View child = itemsContainer.getChildAt(i);
@@ -55,13 +56,13 @@
                     }
                 });
 
-        disableClipping(workspace);
+        setClipChildren(workspace, false);
+        setClipToPadding(workspace, true);
     }
 
     @Override
     public void onTransitionFinished() {
-        restoreClipping(mLauncher.getWorkspace());
-        mLauncher.getWorkspace().forEachVisiblePage(page -> restoreClipping((CellLayout) page));
+        restoreClippings();
         super.onTransitionFinished();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 2770049..89177b6 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -92,6 +92,7 @@
     private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
 
     private View mBackgroundView;
+    private View mEmptyView;
 
     public DesktopTaskView(Context context) {
         this(context, null);
@@ -110,6 +111,7 @@
         super.onFinishInflate();
 
         mBackgroundView = findViewById(R.id.background);
+        mEmptyView = findViewById(R.id.empty_view);
 
         int topMarginPx =
                 mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
@@ -185,6 +187,8 @@
             mSnapshotViewMap.put(task.key.id, snapshotView);
         }
 
+        mEmptyView.setVisibility(mTasks.isEmpty() ? View.VISIBLE : View.GONE);
+
         updateTaskIdContainer();
         updateTaskIdAttributeContainer();
 
diff --git a/quickstep/src/com/android/quickstep/views/LaunchableConstraintLayout.kt b/quickstep/src/com/android/quickstep/views/LaunchableConstraintLayout.kt
new file mode 100644
index 0000000..537eca1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/LaunchableConstraintLayout.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.animation.LaunchableView
+import com.android.systemui.animation.LaunchableViewDelegate
+
+/** A [ConstraintLayout] that also implements [LaunchableView]. */
+open class LaunchableConstraintLayout : ConstraintLayout, LaunchableView {
+    private val delegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+    ) : super(context, attrs, defStyleAttr)
+
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int,
+        defStyleRes: Int,
+    ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    override fun setShouldBlockVisibilityChanges(block: Boolean) {
+        delegate.setShouldBlockVisibilityChanges(block)
+    }
+
+    override fun setVisibility(visibility: Int) {
+        // Note that super.setVisibility() is passed to the delegate upon creation and called by it.
+        // This method is just a passthrough if no animation is in progress, whereas otherwise it
+        // caches the passed value and restores it at the end of the animation.
+        delegate.setVisibility(visibility)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index ff26129..c165acc 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.views;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -171,8 +173,9 @@
     }
 
     @Override
-    public void setModalStateEnabled(boolean isModalState, boolean animate) {
-        if (isModalState) {
+    public void setModalStateEnabled(int taskId, boolean animate) {
+        if (taskId != INVALID_TASK_ID) {
+            setSelectedTask(taskId);
             mActivity.getStateManager().goToState(LauncherState.OVERVIEW_MODAL_TASK, animate);
         } else {
             if (mActivity.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 0d21e60..f578b87 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -303,6 +303,10 @@
             return mDp.getOverviewActionsClaimedSpaceBelow();
         }
 
+        if (mDp.isTablet && FeatureFlags.ENABLE_GRID_ONLY_OVERVIEW.get()) {
+            return mDp.stashedTaskbarSize;
+        }
+
         // Align to bottom of task Rect.
         return mDp.heightPx - mTaskSize.bottom - mDp.overviewActionsTopMarginPx
                 - mDp.overviewActionsHeight;
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5ecc05a..e529b04 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -453,6 +453,7 @@
     protected final Rect mLastComputedTaskSize = new Rect();
     protected final Rect mLastComputedGridSize = new Rect();
     protected final Rect mLastComputedGridTaskSize = new Rect();
+    private TaskView mSelectedTask = null;
     // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
     @Nullable
     protected Float mLastComputedTaskStartPushOutDistance = null;
@@ -1084,6 +1085,16 @@
         super.draw(canvas);
     }
 
+    @Override
+    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+        if (isModal()) {
+            // Do not scroll when clicking on a modal grid task, as it will already be centered
+            // on screen.
+            return false;
+        }
+        return super.requestChildRectangleOnScreen(child, rectangle, immediate);
+    }
+
     public void addSideTaskLaunchCallback(RunnableList callback) {
         if (mSideTaskLaunchCallback == null) {
             mSideTaskLaunchCallback = new RunnableList();
@@ -1272,7 +1283,7 @@
      */
     @Nullable
     public TaskView getTaskViewByTaskId(int taskId) {
-        if (taskId == -1) {
+        if (taskId == INVALID_TASK_ID) {
             return null;
         }
 
@@ -1285,53 +1296,6 @@
         return null;
     }
 
-    /**
-     * Pulls the list of active Tasks from RecentsModel, and finds the most recently active Task
-     * matching a given ComponentName. Then uses that Task (which could be null) with the given
-     * callback.
-     *
-     * Used in various task-switching or splitscreen operations when we need to check if there is a
-     * currently running Task of a certain type and use the most recent one.
-     */
-    public void findLastActiveTaskAndRunCallback(ComponentName componentName,
-            Consumer<Task> callback) {
-        mModel.getTasks(taskGroups -> {
-            Task lastActiveTask = null;
-            // Loop through tasks in reverse, since they are ordered with most-recent tasks last.
-            for (int i = taskGroups.size() - 1; i >= 0; i--) {
-                GroupTask groupTask = taskGroups.get(i);
-                Task task1 = groupTask.task1;
-                if (isInstanceOfComponent(task1, componentName)) {
-                    lastActiveTask = task1;
-                    break;
-                }
-                Task task2 = groupTask.task2;
-                if (isInstanceOfComponent(task2, componentName)) {
-                    lastActiveTask = task2;
-                    break;
-                }
-            }
-
-            callback.accept(lastActiveTask);
-        });
-    }
-
-    /**
-     * Checks if a given Task is the most recently-active Task of type componentName. Used for
-     * selecting already-running Tasks for splitscreen.
-     */
-    public boolean isInstanceOfComponent(@Nullable Task task, ComponentName componentName) {
-        if (task == null) {
-            return false;
-        }
-        // Exclude the task that is already staged
-        if (mSplitHiddenTaskView != null && mSplitHiddenTaskView.getTask().equals(task)) {
-            return false;
-        }
-
-        return task.key.baseIntent.getComponent().equals(componentName);
-    }
-
     public void setOverviewStateEnabled(boolean enabled) {
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
@@ -1967,7 +1931,7 @@
     private void onOrientationChanged() {
         // If overview is in modal state when rotate, reset it to overview state without running
         // animation.
-        setModalStateEnabled(/* isModalState= */ false, /* animate= */ false);
+        setModalStateEnabled(/* taskId= */ INVALID_TASK_ID, /* animate= */ false);
         if (isSplitSelectionActive()) {
             onRotateInSplitSelectionState();
         }
@@ -2040,12 +2004,33 @@
     }
 
     /**
-     * Returns the size of task selected to enter modal state.
+     * Sets the last TaskView selected.
      */
-    public Point getSelectedTaskSize() {
-        mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(),
-                mTempRect);
-        return new Point(mTempRect.width(), mTempRect.height());
+    public void setSelectedTask(int lastSelectedTaskId) {
+        mSelectedTask = getTaskViewByTaskId(lastSelectedTaskId);
+    }
+
+    /**
+     * Returns the bounds of the task selected to enter modal state.
+     */
+    public Rect getSelectedTaskBounds() {
+        if (mSelectedTask == null) {
+            return mLastComputedTaskSize;
+        }
+        return getTaskBounds(mSelectedTask);
+    }
+
+    private Rect getTaskBounds(TaskView taskView) {
+        int selectedPage = indexOfChild(taskView);
+        int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+        int selectedPageScroll = getScrollForPage(selectedPage);
+        boolean isTopRow = taskView != null && mTopRowIdSet.contains(taskView.getTaskViewId());
+        Rect outRect = new Rect(mLastComputedTaskSize);
+        outRect.offset(
+                -(primaryScroll - (selectedPageScroll + getOffsetFromScrollPosition(selectedPage))),
+                (int) (showAsGrid() && ENABLE_GRID_ONLY_OVERVIEW.get() && !isTopRow
+                        ? mTopBottomRowHeightDiff : 0));
+        return outRect;
     }
 
     /** Gets the last computed task size */
@@ -4169,8 +4154,13 @@
 
     private void updatePivots() {
         if (mOverviewSelectEnabled) {
-            setPivotX(mLastComputedTaskSize.centerX());
-            setPivotY(mLastComputedTaskSize.bottom);
+            getModalTaskSize(mTempRect);
+            Rect selectedTaskPosition = getSelectedTaskBounds();
+
+            Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition,
+                    mTempPointF);
+            setPivotX(mTempPointF.x);
+            setPivotY(mTempPointF.y);
         } else {
             getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
                     mActivity.getDeviceProfile(), mTempPointF);
@@ -4183,11 +4173,17 @@
         float offset = mAdjacentPageHorizontalOffset;
         float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
         int count = getChildCount();
+        boolean showAsGrid = showAsGrid();
 
         TaskView runningTask = mRunningTaskViewId == -1 || !mRunningTaskTileHidden
                 ? null : getRunningTaskView();
         int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
         int modalMidpoint = getCurrentPage();
+        boolean isModalGridWithoutFocusedTask =
+                showAsGrid && ENABLE_GRID_ONLY_OVERVIEW.get() && mTaskModalness > 0;
+        if (isModalGridWithoutFocusedTask) {
+            modalMidpoint = indexOfChild(mSelectedTask);
+        }
 
         float midpointOffsetSize = 0;
         float leftOffsetSize = midpoint - 1 >= 0
@@ -4197,7 +4193,6 @@
                 ? getHorizontalOffsetSize(midpoint + 1, midpoint, offset)
                 : 0;
 
-        boolean showAsGrid = showAsGrid();
         float modalMidpointOffsetSize = 0;
         float modalLeftOffsetSize = 0;
         float modalRightOffsetSize = 0;
@@ -4225,23 +4220,34 @@
                     : i < midpoint
                             ? leftOffsetSize
                             : rightOffsetSize;
+            if (isModalGridWithoutFocusedTask) {
+                gridOffsetSize = getHorizontalOffsetSize(i, modalMidpoint, modalOffset);
+                gridOffsetSize = Math.abs(gridOffsetSize) * (i <= modalMidpoint ? 1 : -1);
+            }
             float modalTranslation = i == modalMidpoint
                     ? modalMidpointOffsetSize
                     : showAsGrid
                             ? gridOffsetSize
                             : i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize;
-            float totalTranslation = translation + modalTranslation;
+            float totalTranslationX = translation + modalTranslation;
             View child = getChildAt(i);
-            FloatProperty translationProperty = child instanceof TaskView
+            FloatProperty translationPropertyX = child instanceof TaskView
                     ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
                     : mOrientationHandler.getPrimaryViewTranslate();
-            translationProperty.set(child, totalTranslation);
+            translationPropertyX.set(child, totalTranslationX);
             if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
                 runActionOnRemoteHandles(
                         remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
-                                .taskPrimaryTranslation.value = totalTranslation);
+                                .taskPrimaryTranslation.value = totalTranslationX);
                 redrawLiveTile();
             }
+
+            if (showAsGrid && ENABLE_GRID_ONLY_OVERVIEW.get() && child instanceof TaskView) {
+                float totalTranslationY = getVerticalOffsetSize(i, modalOffset);
+                FloatProperty translationPropertyY =
+                        ((TaskView) child).getSecondaryTaskOffsetTranslationProperty();
+                translationPropertyY.set(child, totalTranslationY);
+            }
         }
         updateCurveProperties();
     }
@@ -4339,6 +4345,38 @@
         return distanceToOffscreen * offsetProgress;
     }
 
+    /**
+     * Computes the vertical distance to offset a given child such that it is completely offscreen.
+     *
+     * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
+     */
+    private float getVerticalOffsetSize(int childIndex, float offsetProgress) {
+        if (offsetProgress == 0 || !(showAsGrid() && ENABLE_GRID_ONLY_OVERVIEW.get())
+                || mSelectedTask == null) {
+            // Don't bother calculating everything below if we won't offset vertically.
+            return 0;
+        }
+
+        // First, get the position of the task relative to the top row.
+        TaskView child = getTaskViewAt(childIndex);
+        Rect taskPosition = getTaskBounds(child);
+
+        boolean isSelectedTaskTopRow = mTopRowIdSet.contains(mSelectedTask.getTaskViewId());
+        boolean isChildTopRow = mTopRowIdSet.contains(child.getTaskViewId());
+        // Whether the task should be shifted to the top.
+        boolean isTopShift = !isSelectedTaskTopRow && isChildTopRow;
+        boolean isBottomShift = isSelectedTaskTopRow && !isChildTopRow;
+
+        // Next, calculate the distance to move the task off screen at scale = 1.
+        float distanceToOffscreen = 0;
+        if (isTopShift) {
+            distanceToOffscreen = -taskPosition.bottom;
+        } else if (isBottomShift) {
+            distanceToOffscreen = mActivity.getDeviceProfile().heightPx - taskPosition.top;
+        }
+        return distanceToOffscreen * offsetProgress;
+    }
+
     protected void setTaskViewsResistanceTranslation(float translation) {
         mTaskViewsSecondaryTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
@@ -4438,9 +4476,8 @@
      * Resets the visuals when exit modal state.
      */
     public void resetModalVisuals() {
-        TaskView taskView = getCurrentPageTaskView();
-        if (taskView != null) {
-            taskView.getThumbnail().getTaskOverlay().resetModalVisuals();
+        if (mSelectedTask != null) {
+            mSelectedTask.getThumbnail().getTaskOverlay().resetModalVisuals();
         }
     }
 
@@ -5442,6 +5479,9 @@
         if (mOverviewSelectEnabled != overviewSelectEnabled) {
             mOverviewSelectEnabled = overviewSelectEnabled;
             updatePivots();
+            if (!mOverviewSelectEnabled) {
+                setSelectedTask(INVALID_TASK_ID);
+            }
         }
     }
 
@@ -5512,7 +5552,9 @@
     private void setTaskModalness(float modalness) {
         mTaskModalness = modalness;
         updatePageOffsets();
-        if (getCurrentPageTaskView() != null) {
+        if (mSelectedTask != null) {
+            mSelectedTask.setModalness(modalness);
+        } else if (getCurrentPageTaskView() != null) {
             getCurrentPageTaskView().setModalness(modalness);
         }
         // Only show actions view when it's modal for in-place landscape mode.
@@ -5527,7 +5569,7 @@
     }
 
     /** Enables or disables modal state for RecentsView */
-    public abstract void setModalStateEnabled(boolean isModalState, boolean animate);
+    public abstract void setModalStateEnabled(int taskId, boolean animate);
 
     public TaskOverlayFactory getTaskOverlayFactory() {
         return mTaskOverlayFactory;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index df67b99..fb85605 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1445,6 +1445,11 @@
                 TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
     }
 
+    public FloatProperty<TaskView> getSecondaryTaskOffsetTranslationProperty() {
+        return getPagedOrientationHandler().getSecondaryValue(
+                TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
+    }
+
     public FloatProperty<TaskView> getTaskResistanceTranslationProperty() {
         return getPagedOrientationHandler().getSecondaryValue(
                 TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index b109e8a..9e28608 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -374,9 +374,7 @@
 
     @UiThread
     protected void applyIconAndLabel(ItemInfoWithIcon info) {
-        boolean useTheme = mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER
-                || mDisplay == DISPLAY_TASKBAR;
-        int flags = useTheme ? FLAG_THEMED : 0;
+        int flags = shouldUseTheme() ? FLAG_THEMED : 0;
         if (mHideBadge) {
             flags |= FLAG_NO_BADGE;
         }
@@ -388,6 +386,11 @@
         applyLabel(info);
     }
 
+    protected boolean shouldUseTheme() {
+        return mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER
+                || mDisplay == DISPLAY_TASKBAR;
+    }
+
     @UiThread
     private void applyLabel(ItemInfoWithIcon info) {
         setText(info.title);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index e63b054..90b2374 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -190,10 +190,10 @@
     public final int hotseatQsbVisualHeight;
     private final int hotseatQsbShadowHeight;
     public int hotseatBorderSpace;
-    private int minHotseatIconSpacePx;
-    private int minHotseatQsbWidthPx;
-    private final int maxHotseatIconSpacePx;
-    private int inlineNavButtonsEndSpacing;
+    private final int mMinHotseatIconSpacePx;
+    private final int mMinHotseatQsbWidthPx;
+    private final int mMaxHotseatIconSpacePx;
+    private final int mInlineNavButtonsEndSpacingPx;
 
     // Bottom sheets
     public int bottomSheetTopPadding;
@@ -490,7 +490,8 @@
         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
         updateHotseatSizes(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
         if (areNavButtonsInline && !isPhone) {
-            inlineNavButtonsEndSpacing = res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
+            mInlineNavButtonsEndSpacingPx =
+                    res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
             /*
              * 3 nav buttons +
              * Spacing between nav buttons +
@@ -498,9 +499,9 @@
              */
             hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
                     + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
-                    + inlineNavButtonsEndSpacing;
+                    + mInlineNavButtonsEndSpacingPx;
         } else {
-            inlineNavButtonsEndSpacing = 0;
+            mInlineNavButtonsEndSpacingPx = 0;
             hotseatBarEndOffset = 0;
         }
 
@@ -547,9 +548,9 @@
                 cellLayoutPadding);
         updateWorkspacePadding();
 
-        minHotseatIconSpacePx = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space);
-        minHotseatQsbWidthPx = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width);
-        maxHotseatIconSpacePx = areNavButtonsInline
+        mMinHotseatIconSpacePx = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space);
+        mMinHotseatQsbWidthPx = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width);
+        mMaxHotseatIconSpacePx = areNavButtonsInline
                 ? res.getDimensionPixelSize(R.dimen.max_hotseat_icon_space) : Integer.MAX_VALUE;
         // Hotseat and QSB width depends on updated cellSize and workspace padding
         recalculateHotseatWidthAndBorderSpace();
@@ -661,39 +662,39 @@
         }
 
         // The side space with inline buttons should be what is defined in InvariantDeviceProfile
-        int sideSpace = inlineNavButtonsEndSpacing;
-        int maxHotseatWidth = availableWidthPx - sideSpace - hotseatBarEndOffset;
-        int maxHotseatIconsWidth = maxHotseatWidth - (isQsbInline ? hotseatQsbWidth : 0);
-        hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidth,
+        int sideSpacePx = mInlineNavButtonsEndSpacingPx;
+        int maxHotseatWidthPx = availableWidthPx - sideSpacePx - hotseatBarEndOffset;
+        int maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
+        hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
                 (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
 
-        if (hotseatBorderSpace >= minHotseatIconSpacePx) {
+        if (hotseatBorderSpace >= mMinHotseatIconSpacePx) {
             return;
         }
 
         // Border space can't be less than the minimum
-        hotseatBorderSpace = minHotseatIconSpacePx;
+        hotseatBorderSpace = mMinHotseatIconSpacePx;
         int requiredWidth = getHotseatRequiredWidth();
 
         // If there is an inline qsb, change its size
         if (isQsbInline) {
-            hotseatQsbWidth -= requiredWidth - maxHotseatWidth;
-            if (hotseatQsbWidth >= minHotseatQsbWidthPx) {
+            hotseatQsbWidth -= requiredWidth - maxHotseatWidthPx;
+            if (hotseatQsbWidth >= mMinHotseatQsbWidthPx) {
                 return;
             }
 
             // QSB can't be less than the minimum
-            hotseatQsbWidth = minHotseatQsbWidthPx;
+            hotseatQsbWidth = mMinHotseatQsbWidthPx;
         }
 
-        maxHotseatIconsWidth = maxHotseatWidth - (isQsbInline ? hotseatQsbWidth : 0);
+        maxHotseatIconsWidthPx = maxHotseatWidthPx - (isQsbInline ? hotseatQsbWidth : 0);
 
         // If it still doesn't fit, start removing icons
         do {
             numShownHotseatIcons--;
-            hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidth,
+            hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidthPx,
                     (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
-        } while (hotseatBorderSpace < minHotseatIconSpacePx && numShownHotseatIcons > 1);
+        } while (hotseatBorderSpace < mMinHotseatIconSpacePx && numShownHotseatIcons > 1);
 
     }
 
@@ -993,10 +994,10 @@
      */
     private int calculateHotseatBorderSpace(float hotseatWidthPx, int numExtraBorder) {
         float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
-        int hotseatBorderSpace =
+        int hotseatBorderSpacePx =
                 (int) (hotseatWidthPx - hotseatIconsTotalPx)
                         / (numShownHotseatIcons - 1 + numExtraBorder);
-        return Math.min(hotseatBorderSpace, maxHotseatIconSpacePx);
+        return Math.min(hotseatBorderSpacePx, mMaxHotseatIconSpacePx);
     }
 
 
@@ -1319,7 +1320,7 @@
             int endSpacing;
             // Hotseat aligns to the left with nav buttons
             if (hotseatBarEndOffset > 0) {
-                startSpacing = inlineNavButtonsEndSpacing;
+                startSpacing = mInlineNavButtonsEndSpacingPx;
                 endSpacing = availableWidthPx - hotseatWidth - startSpacing + hotseatBorderSpace;
             } else {
                 startSpacing = (availableWidthPx - hotseatWidth) / 2;
diff --git a/src/com/android/launcher3/FastScrollRecyclerView.java b/src/com/android/launcher3/FastScrollRecyclerView.java
index e1a216e..c16b319 100644
--- a/src/com/android/launcher3/FastScrollRecyclerView.java
+++ b/src/com/android/launcher3/FastScrollRecyclerView.java
@@ -20,7 +20,6 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
@@ -53,21 +52,9 @@
         super(context, attrs, defStyleAttr);
     }
 
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        if (mScrollbar == null || !mScrollbar.hasRecyclerView()) {
-            bindFastScrollbar();
-        }
-    }
-
-    public void bindFastScrollbar() {
-        ViewGroup parent = (ViewGroup) getParent().getParent();
-        if (parent.findViewById(R.id.fast_scroller) == null) {
-            parent = (ViewGroup) parent.getParent();
-        }
-        mScrollbar = parent.findViewById(R.id.fast_scroller);
-        mScrollbar.setRecyclerView(this, parent.findViewById(R.id.fast_scroller_popup));
+    public void bindFastScrollbar(RecyclerViewFastScroller scrollbar) {
+        mScrollbar = scrollbar;
+        mScrollbar.setRecyclerView(this);
         onUpdateScrollbar(0);
     }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2fb0fa6..604c1b8 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_DEVICE_PROFILE_LOGGING;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TWO_PANEL_HOME;
@@ -94,6 +93,8 @@
     public static final int TYPE_MULTI_DISPLAY = 1;
     public static final int TYPE_TABLET = 2;
 
+    private static final String KEY_IDP_GRID_NAME = "idp_grid_name";
+
     private static final float ICON_SIZE_DEFINED_IN_APP_DP = 48;
 
     // Constants that affects the interpolation curve between statically defined device profile
@@ -206,7 +207,8 @@
         String gridName = getCurrentGridName(context);
         String newGridName = initGrid(context, gridName);
         if (!newGridName.equals(gridName)) {
-            LauncherPrefs.get(context).put(GRID_NAME, newGridName);
+            LauncherPrefs.getPrefs(context).edit().putString(KEY_IDP_GRID_NAME, newGridName)
+                    .apply();
         }
         new DeviceGridState(this).writeToPrefs(context);
 
@@ -314,7 +316,7 @@
     }
 
     public static String getCurrentGridName(Context context) {
-        return LauncherPrefs.get(context).get(GRID_NAME);
+        return LauncherPrefs.getPrefs(context).getString(KEY_IDP_GRID_NAME, null);
     }
 
     private String initGrid(Context context, String gridName) {
@@ -456,8 +458,9 @@
 
 
     public void setCurrentGrid(Context context, String gridName) {
-        LauncherPrefs.get(context).put(GRID_NAME, gridName);
-        MAIN_EXECUTOR.execute(() -> onConfigChanged(context.getApplicationContext()));
+        Context appContext = context.getApplicationContext();
+        LauncherPrefs.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply();
+        MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext));
     }
 
     private Object[] toModelState() {
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index befaa64..2e07e30 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -4,8 +4,6 @@
 import android.content.SharedPreferences
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener
 import androidx.annotation.VisibleForTesting
-import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
-import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
 import com.android.launcher3.allapps.WorkProfileManager
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
@@ -22,10 +20,11 @@
 class LauncherPrefs(private val context: Context) {
 
     /** Wrapper around `getInner` for a `ContextualItem` */
-    fun <T> get(item: ContextualItem<T>): T = getInner(item, item.defaultValueFromContext(context))
+    fun <T : Any> get(item: ContextualItem<T>): T =
+        getInner(item, item.defaultValueFromContext(context))
 
     /** Wrapper around `getInner` for an `Item` */
-    fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
+    fun <T : Any> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
 
     /**
      * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
@@ -33,11 +32,11 @@
      * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
      */
     @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
-    private fun <T> getInner(item: Item, default: T): T {
+    private fun <T : Any> getInner(item: Item, default: T): T {
         val sp = context.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
 
-        return when (item.type) {
-            String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
+        return when (default::class.java) {
+            String::class.java -> sp.getString(item.sharedPrefKey, default as String)
             Boolean::class.java,
             java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
             Int::class.java,
@@ -46,10 +45,11 @@
             java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
             Long::class.java,
             java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
-            Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
+            Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as Set<String>)
             else ->
                 throw IllegalArgumentException(
-                    "item type: ${item.type}" + " is not compatible with sharedPref methods"
+                    "item type: ${default::class.java}" +
+                        " is not compatible with sharedPref methods"
                 )
         }
             as T
@@ -224,36 +224,39 @@
             backedUpItem(RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
         @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
         @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
-        @JvmField val GRID_NAME = ConstantItem("idp_grid_name", true, null, String::class.java)
         @JvmField
         val ALLOW_ROTATION =
-            backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY, Boolean::class.java) {
+            backedUpItem(RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY) {
                 RotationHelper.getAllowRotationDefaultValue(DisplayController.INSTANCE.get(it).info)
             }
 
         @VisibleForTesting
         @JvmStatic
         fun <T> backedUpItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
-            ConstantItem(sharedPrefKey, true, defaultValue)
+            ConstantItem(sharedPrefKey, LauncherFiles.SHARED_PREFERENCES_KEY, defaultValue)
 
         @JvmStatic
         fun <T> backedUpItem(
             sharedPrefKey: String,
-            type: Class<out T>,
             defaultValueFromContext: (c: Context) -> T
-        ): ContextualItem<T> = ContextualItem(sharedPrefKey, true, defaultValueFromContext, type)
+        ): ContextualItem<T> =
+            ContextualItem(
+                sharedPrefKey,
+                LauncherFiles.SHARED_PREFERENCES_KEY,
+                defaultValueFromContext
+            )
 
         @VisibleForTesting
         @JvmStatic
         fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): ConstantItem<T> =
-            ConstantItem(sharedPrefKey, false, defaultValue)
+            ConstantItem(sharedPrefKey, LauncherFiles.DEVICE_PREFERENCES_KEY, defaultValue)
 
         @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
         @JvmStatic
         fun getPrefs(context: Context): SharedPreferences {
             // Use application context for shared preferences, so we use single cached instance
             return context.applicationContext.getSharedPreferences(
-                SHARED_PREFERENCES_KEY,
+                LauncherFiles.SHARED_PREFERENCES_KEY,
                 Context.MODE_PRIVATE
             )
         }
@@ -263,7 +266,7 @@
         fun getDevicePrefs(context: Context): SharedPreferences {
             // Use application context for shared preferences, so we use a single cached instance
             return context.applicationContext.getSharedPreferences(
-                DEVICE_PREFERENCES_KEY,
+                LauncherFiles.DEVICE_PREFERENCES_KEY,
                 Context.MODE_PRIVATE
             )
         }
@@ -272,26 +275,21 @@
 
 abstract class Item {
     abstract val sharedPrefKey: String
-    abstract val isBackedUp: Boolean
-    abstract val type: Class<*>
-    val sharedPrefFile: String = if (isBackedUp) SHARED_PREFERENCES_KEY else DEVICE_PREFERENCES_KEY
+    abstract val sharedPrefFile: String
 
     fun <T> to(value: T): Pair<Item, T> = Pair(this, value)
 }
 
 data class ConstantItem<T>(
     override val sharedPrefKey: String,
-    override val isBackedUp: Boolean,
-    val defaultValue: T,
-    // The default value can be null. If so, the type needs to be explicitly stated, or else NPE
-    override val type: Class<out T> = defaultValue!!::class.java
+    override val sharedPrefFile: String,
+    val defaultValue: T
 ) : Item()
 
 data class ContextualItem<T>(
     override val sharedPrefKey: String,
-    override val isBackedUp: Boolean,
-    private val defaultSupplier: (c: Context) -> T,
-    override val type: Class<out T>
+    override val sharedPrefFile: String,
+    private val defaultSupplier: (c: Context) -> T
 ) : Item() {
     private var default: T? = null
 
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 81fbe79..b8d13ed 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -226,6 +226,20 @@
     }
 
     /**
+     * Returns whether taskbar global drag is disallowed in this state.
+     */
+    public boolean disallowTaskbarGlobalDrag() {
+        return false;
+    }
+
+    /**
+     * Returns whether the taskbar shortcut should trigger split selection mode.
+     */
+    public boolean allowTaskbarInitialSplitSelection() {
+        return false;
+    }
+
+    /**
      * Fraction shift in the vertical translation UI and related properties
      *
      * @see com.android.launcher3.allapps.AllAppsTransitionController
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
index d671c7d..a2c5255 100644
--- a/src/com/android/launcher3/MultipageCellLayout.java
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -38,6 +38,8 @@
 
     private View mSeam;
 
+    private boolean mSeamWasAdded = false;
+
     public MultipageCellLayout(Context context) {
         this(context, null);
     }
@@ -65,44 +67,71 @@
     }
 
     @Override
+    boolean createAreaForResize(int cellX, int cellY, int spanX, int spanY, View dragView,
+            int[] direction, boolean commit) {
+        return simulateSeam(
+                () -> super.createAreaForResize(cellX, cellY, spanX, spanY, dragView, direction,
+                        commit));
+    }
+
+    @Override
+    void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
+        simulateSeam(() -> {
+            super.regionToCenterPoint(cellX, cellY, spanX, spanY, result);
+            return 0;
+        });
+    }
+
+    @Override
     ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
             int spanX, int spanY) {
-        return simulateSeam(
+        return removeSeamFromSolution(simulateSeam(
                 () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
-                        spanY));
+                        spanY)));
     }
 
     @Override
     protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
             ItemConfiguration solution) {
-        return simulateSeam(
+        return removeSeamFromSolution(simulateSeam(
                 () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
-                        direction, dragView, decX, solution));
+                        direction, dragView, decX, solution)));
     }
 
     @Override
     public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY,
             View dragView) {
-        return simulateSeam(
-                () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView));
+        return removeSeamFromSolution(simulateSeam(
+                () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
     }
 
-    protected ItemConfiguration simulateSeam(Supplier<ItemConfiguration> f) {
+    void addSeam() {
         CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mCountX / 2, 0, 1, mCountY);
+        mSeamWasAdded = true;
         lp.canReorder = false;
         mCountX++;
         mShortcutsAndWidgets.addViewInLayout(mSeam, lp);
-        GridOccupancy auxGrid = mOccupied;
         mOccupied = createGridOccupancy();
         mTmpOccupied = new GridOccupancy(mCountX, mCountY);
+    }
 
-        ItemConfiguration res = removeSeamFromSolution(f.get());
-
+    void removeSeam() {
         mCountX--;
         mShortcutsAndWidgets.removeViewInLayout(mSeam);
-        mOccupied = auxGrid;
         mTmpOccupied = new GridOccupancy(mCountX, mCountY);
+        mSeamWasAdded = false;
+    }
+
+    protected <T> T simulateSeam(Supplier<T> f) {
+        if (mSeamWasAdded) {
+            return f.get();
+        }
+        GridOccupancy auxGrid = mOccupied;
+        addSeam();
+        T res = f.get();
+        removeSeam();
+        mOccupied = auxGrid;
         return res;
     }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b32ff3c..67bc7fc 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -38,6 +38,7 @@
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.AdaptiveIconDrawable;
@@ -343,6 +344,21 @@
     }
 
     /**
+     * Sets the x and y pivots for scaling from one Rect to another.
+     *
+     * @param src the source rectangle to scale from.
+     * @param dst the destination rectangle to scale to.
+     * @param outPivot the pivots set for scaling from src to dst.
+     */
+    public static void getPivotsForScalingRectToRect(Rect src, Rect dst, PointF outPivot) {
+        float pivotXPct = ((float) src.left - dst.left) / ((float) dst.width() - src.width());
+        outPivot.x = dst.left + dst.width() * pivotXPct;
+
+        float pivotYPct = ((float) src.top - dst.top) / ((float) dst.height() - src.height());
+        outPivot.y = dst.top + dst.height() * pivotYPct;
+    }
+
+    /**
      * Maps t from one range to another range.
      * @param t The value to map.
      * @param fromMin The lower bound of the range that t is being mapped from.
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 45d532d..8fbe997 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -134,6 +134,8 @@
     protected AllAppsPagedView mViewPager;
     protected FloatingHeaderView mHeader;
     protected View mBottomSheetBackground;
+    protected RecyclerViewFastScroller mFastScroller;
+
     /**
      * View that defines the search box. Result is rendered inside {@link #mSearchRecyclerView}.
      */
@@ -219,6 +221,8 @@
         mBottomSheetBackground = findViewById(R.id.bottom_sheet_background);
         mBottomSheetHandleArea = findViewById(R.id.bottom_sheet_handle_area);
         mSearchRecyclerView = findViewById(R.id.search_results_list_view);
+        mFastScroller = findViewById(R.id.fast_scroller);
+        mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
 
         // Add the search box above everything else.
         mSearchContainer = inflateSearchBox();
@@ -302,6 +306,7 @@
         if (!mSearchTransitionController.isRunning() && goingToSearch == isSearching()) {
             return;
         }
+        mFastScroller.setVisibility(goingToSearch ? INVISIBLE : VISIBLE);
         if (goingToSearch) {
             // Fade out the button to pause work apps.
             mWorkManager.onActivePageChanged(SEARCH);
@@ -411,7 +416,7 @@
             return;
         }
         if (mAH.get(currentActivePage).mRecyclerView != null) {
-            mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar();
+            mAH.get(currentActivePage).mRecyclerView.bindFastScrollbar(mFastScroller);
         }
         // Header keeps track of active recycler view to properly render header protection.
         mHeader.setActiveRV(currentActivePage);
@@ -494,7 +499,7 @@
         if (isSearchBarOnBottom()) {
             // Keep the scroller above the search bar.
             RelativeLayout.LayoutParams scrollerLayoutParams =
-                    (LayoutParams) findViewById(R.id.fast_scroller).getLayoutParams();
+                    (LayoutParams) mFastScroller.getLayoutParams();
             scrollerLayoutParams.addRule(RelativeLayout.ABOVE, R.id.search_container_all_apps);
             scrollerLayoutParams.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
             scrollerLayoutParams.bottomMargin = getResources().getDimensionPixelSize(
@@ -1055,12 +1060,6 @@
                 : mViewPager == null ? AdapterHolder.MAIN : mViewPager.getNextPage();
     }
 
-    /** The scroll bar for the active apps recycler view. */
-    public RecyclerViewFastScroller getScrollBar() {
-        AllAppsRecyclerView rv = getActiveAppsRecyclerView();
-        return rv == null ? null : rv.getScrollbar();
-    }
-
     /**
      * Adds an update listener to animator that adds springs to the animation.
      */
@@ -1252,6 +1251,7 @@
         void setup(@NonNull View rv, @Nullable Predicate<ItemInfo> matcher) {
             mAppsList.updateItemFilter(matcher);
             mRecyclerView = (AllAppsRecyclerView) rv;
+            mRecyclerView.bindFastScrollbar(mFastScroller);
             mRecyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
             mRecyclerView.setApps(mAppsList);
             mRecyclerView.setLayoutManager(mLayoutManager);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 6b45fe6..7c5c003 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -43,7 +43,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.RecyclerViewFastScroller;
 
 import java.util.List;
 
@@ -274,10 +273,6 @@
                 : getRootWindowInsets().getSystemWindowInsetBottom();
     }
 
-    public RecyclerViewFastScroller getScrollbar() {
-        return mScrollbar;
-    }
-
     @Override
     public boolean hasOverlappingRendering() {
         return false;
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 92c017c..4430a94 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -200,6 +200,7 @@
                 ? FLAG_DARK_NAV : FLAG_LIGHT_NAV;
 
         setShiftRange(dp.allAppsShiftRange);
+        mAllAppScale.value = 1;
         mLauncher.addOnDeviceProfileChangeListener(this);
         mVibratorWrapper = VibratorWrapper.INSTANCE.get(mLauncher.getApplicationContext());
     }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index c89a461..0ec036c 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -355,6 +355,9 @@
     public static final BooleanFlag ENABLE_ICON_IN_TEXT_HEADER = getDebugFlag(
             "ENABLE_ICON_IN_TEXT_HEADER", false, "Show icon in textheader");
 
+    public static final BooleanFlag ENABLE_APP_ICON_FOR_INLINE_SHORTCUTS = getDebugFlag(
+            "ENABLE_APP_ICON_IN_INLINE_SHORTCUTS", false, "Show app icon for inline shortcut");
+
     public static final BooleanFlag SHOW_DOT_PAGINATION = getDebugFlag(
             "SHOW_DOT_PAGINATION", false, "Enable showing dot pagination in workspace");
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index e10fdf5..46c8e81 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -83,6 +83,7 @@
     protected final int mRegistrationX;
     protected final int mRegistrationY;
     private final float mInitialScale;
+    private final float mEndScale;
     protected final float mScaleOnDrop;
     protected final int[] mTempLoc = new int[2];
 
@@ -158,7 +159,7 @@
             setClipToPadding(false);
         }
 
-        final float scale = (width + finalScaleDps) / width;
+        mEndScale = (width + finalScaleDps) / width;
 
         // Set the initial scale to avoid any jumps
         setScaleX(initialScale);
@@ -169,8 +170,8 @@
         mAnim.setDuration(VIEW_ZOOM_DURATION);
         mAnim.addUpdateListener(animation -> {
             final float value = (Float) animation.getAnimatedValue();
-            setScaleX(initialScale + (value * (scale - initialScale)));
-            setScaleY(initialScale + (value * (scale - initialScale)));
+            setScaleX(Utilities.mapRange(value, initialScale, mEndScale));
+            setScaleY(Utilities.mapRange(value, initialScale, mEndScale));
             if (!isAttachedToWindow()) {
                 animation.cancel();
             }
@@ -508,6 +509,10 @@
         return mInitialScale;
     }
 
+    public float getEndScale() {
+        return mEndScale;
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         return false;
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index f7837f5..b6f6223 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -313,7 +313,7 @@
      */
     default boolean startActivitySafely(
             View v, Intent intent, @Nullable ItemInfo item) {
-
+        Preconditions.assertUIThread();
         Context context = (Context) this;
         if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) {
             Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index ead6886..a941833 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -171,12 +171,14 @@
         ta.recycle();
     }
 
-    /** @return whether there is a RecyclerView bound to this scroller. */
-    public boolean hasRecyclerView() {
-        return mRv != null;
+    /** Sets the popup view to show while the scroller is being dragged */
+    public void setPopupView(TextView popupView) {
+        mPopupView = popupView;
+        mPopupView.setBackground(
+                new FastScrollThumbDrawable(mThumbPaint, Utilities.isRtl(getResources())));
     }
 
-    public void setRecyclerView(FastScrollRecyclerView rv, TextView popupView) {
+    public void setRecyclerView(FastScrollRecyclerView rv) {
         if (mRv != null && mOnScrollListener != null) {
             mRv.removeOnScrollListener(mOnScrollListener);
         }
@@ -194,10 +196,6 @@
                 mRv.onUpdateScrollbar(dy);
             }
         });
-
-        mPopupView = popupView;
-        mPopupView.setBackground(
-                new FastScrollThumbDrawable(mThumbPaint, Utilities.isRtl(getResources())));
     }
 
     public void reattachThumbToScroll() {
@@ -336,7 +334,7 @@
     }
 
     public void onDraw(Canvas canvas) {
-        if (mThumbOffsetY < 0) {
+        if (mThumbOffsetY < 0 || mRv == null) {
             return;
         }
         int saveCount = canvas.save();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 545e661..d72914b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -159,7 +159,7 @@
                     WidgetsRecyclerView searchRecyclerView =
                             mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView;
                     if (mIsInSearchMode && searchRecyclerView != null) {
-                        searchRecyclerView.bindFastScrollbar();
+                        searchRecyclerView.bindFastScrollbar(mFastScroller);
                     }
                 }
 
@@ -253,6 +253,8 @@
         if (mIsTwoPane) {
             mFastScroller.setVisibility(GONE);
         }
+        mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
+
         mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view));
         mAdapters.get(AdapterHolder.SEARCH).setup(findViewById(R.id.search_widgets_list_view));
         if (mHasWorkProfile) {
@@ -373,7 +375,7 @@
     }
 
     private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) {
-        recyclerView.bindFastScrollbar();
+        recyclerView.bindFastScrollbar(mFastScroller);
         if (mCurrentWidgetsRecyclerView != recyclerView) {
             // Only reset the scroll position & expanded apps if the currently shown recycler view
             // has been updated.
@@ -583,7 +585,7 @@
             workUserAdapterHolder.mWidgetsListAdapter.setWidgets(allWidgets);
             onActivePageChanged(mViewPager.getCurrentPage());
         } else {
-            updateRecyclerViewVisibility(primaryUserAdapterHolder);
+            onActivePageChanged(0);
         }
         // Update recommended widgets section so that it occupies appropriate space on screen to
         // leave enough space for presence/absence of mNoWidgetsView.
@@ -1021,6 +1023,7 @@
             mWidgetsRecyclerView.setClipToOutline(true);
             mWidgetsRecyclerView.setClipChildren(false);
             mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
+            mWidgetsRecyclerView.bindFastScrollbar(mFastScroller);
             mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator);
             mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
             if (!mIsTwoPane) {
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
index d40a7bc..31e8d30 100644
--- a/tests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -13,7 +13,7 @@
 private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
 private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
 private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
-private val TEST_CONTEXTUAL_ITEM = ContextualItem("4", true, { true }, Boolean::class.java)
+private val TEST_CONTEXTUAL_ITEM = LauncherPrefs.backedUpItem("4") { true }
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)