Merge "Simplifying some scrollbar binding logic in all-apps" into tm-qpr-dev
diff --git a/quickstep/res/drawable/close_icon.xml b/quickstep/res/drawable/close_icon.xml
deleted file mode 100644
index 07f4336..0000000
--- a/quickstep/res/drawable/close_icon.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="?attr/colorControlNormal">
-  <path
-      android:fillColor="#909090"
-      android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
-</vector>
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 6d778ef..ff7c138 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
-import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
 
 import android.animation.Animator;
 
@@ -93,7 +92,8 @@
     }
 
     private void animateToRecentsState(RecentsState toState) {
-        Animator anim = createAnimToRecentsState(toState, TASKBAR_STASH_DURATION);
+        Animator anim = createAnimToRecentsState(toState,
+                mControllers.taskbarStashController.getStashDuration());
         if (anim != null) {
             anim.start();
         }
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 ac584bf..3046076 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -388,12 +388,16 @@
     }
 
     @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);
     }
 
     @Override
+    protected void onIconLayoutBoundsChanged() {
+        mTaskbarLauncherStateController.resetIconAlignment();
+    }
+
+    @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         super.dumpLogs(prefix, pw);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index 0f25ba1..f082fc6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
-import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.PrintWriter;
 
@@ -152,6 +151,14 @@
         }
     }
 
+    /**
+     * Returns the stashed handle bounds.
+     * @param out The destination rect.
+     */
+    public void getStashedHandleBounds(Rect out) {
+        out.set(mStashedHandleBounds);
+    }
+
     private void initRegionSampler() {
         mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
                 new RegionSamplingHelper.SamplingCallback() {
@@ -194,16 +201,19 @@
      */
     public Animator createRevealAnimToIsStashed(boolean isStashed) {
         Rect visualBounds = new Rect(mControllers.taskbarViewController.getIconLayoutBounds());
+        float startRadius = mStashedHandleRadius;
 
         if (DisplayController.isTransientTaskbar(mActivity)) {
             // Account for the full visual height of the transient taskbar.
             int heightDiff = (mTaskbarSize - visualBounds.height()) / 2;
             visualBounds.top -= heightDiff;
             visualBounds.bottom += heightDiff;
+
+            startRadius = visualBounds.height() / 2f;
         }
 
         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
-                mStashedHandleRadius, mStashedHandleRadius, visualBounds, mStashedHandleBounds);
+                startRadius, mStashedHandleRadius, visualBounds, mStashedHandleBounds);
 
         boolean isReversed = !isStashed;
         boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
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 82f27ae..37d9090 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -20,7 +20,9 @@
 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
 import com.android.launcher3.anim.Interpolators
 import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
@@ -29,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
 
@@ -51,6 +54,12 @@
     private val invertedLeftCornerPath: Path = Path()
     private val invertedRightCornerPath: Path = Path()
 
+    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
@@ -98,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)
 
@@ -110,13 +119,25 @@
             canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
             canvas.drawPath(invertedRightCornerPath, paint)
         } else {
-            // Approximates the stash/unstash animation to transform the background.
-            val scaleFactor = backgroundHeight / maxBackgroundHeight
-            val width = transientBackgroundBounds.width()
-            val widthScale = mapToRange(scaleFactor, 0f, 1f, 0.2f, 1f, Interpolators.LINEAR)
-            val newWidth = widthScale * width
-            val delta = width - newWidth
-            canvas.translate(0f, bottomMargin * ((1f - scaleFactor) / 2f))
+            // 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 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 =
@@ -128,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 6432119..0b86155 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
-import static com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_DURATION;
 import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
 import static com.android.systemui.animation.Interpolators.EMPHASIZED;
 
@@ -39,8 +38,8 @@
 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;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.views.RecentsView;
@@ -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());
                 }
             };
 
@@ -139,8 +137,7 @@
         mIconAlphaForHome = mControllers.taskbarViewController
                 .getTaskbarIconAlpha().get(ALPHA_INDEX_HOME);
 
-        mIconAlignment.finishAnimation();
-        onIconAlignmentRatioChanged();
+        resetIconAlignment();
 
         mLauncher.getStateManager().addStateListener(mStateListener);
 
@@ -234,7 +231,7 @@
     }
 
     public void applyState() {
-        applyState(TASKBAR_STASH_DURATION);
+        applyState(mControllers.taskbarStashController.getStashDuration());
     }
 
     public void applyState(long duration) {
@@ -242,7 +239,7 @@
     }
 
     public Animator applyState(boolean start) {
-        return applyState(TASKBAR_STASH_DURATION, start);
+        return applyState(mControllers.taskbarStashController.getStashDuration(), start);
     }
 
     public Animator applyState(long duration, boolean start) {
@@ -329,8 +326,17 @@
                         + mTaskbarBackgroundAlpha.value
                         + " -> " + backgroundAlpha + ": " + duration);
             }
-            animatorSet.play(mTaskbarBackgroundAlpha.animateToValue(backgroundAlpha)
-                    .setDuration(duration));
+
+            Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha
+                    .animateToValue(backgroundAlpha)
+                    .setDuration(duration);
+            // Add a single frame delay to the taskbar bg to avoid too many moving parts during the
+            // app launch animation.
+            taskbarBackgroundAlpha.setStartDelay(
+                    (hasAnyFlag(changedFlags, FLAG_RESUMED) && !goingToLauncher)
+                            ? RefreshRateTracker.getSingleFrameMs(mLauncher)
+                            : 0);
+            animatorSet.play(taskbarBackgroundAlpha);
         }
 
         float cornerRoundness = goingToLauncher ? 0 : 1;
@@ -433,6 +439,14 @@
         return (mState & FLAGS_LAUNCHER) != 0;
     }
 
+    /**
+     * Resets and updates the icon alignment.
+     */
+    protected void resetIconAlignment() {
+        mIconAlignment.finishAnimation();
+        onIconAlignmentRatioChanged();
+    }
+
     private void onIconAlignmentRatioChanged() {
         float currentValue = mIconAlphaForHome.getValue();
         boolean taskbarWillBeVisible = mIconAlignment.value < 1;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 2f69b07..50f5d9e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -17,8 +17,10 @@
 
 import static android.view.HapticFeedbackConstants.LONG_PRESS;
 
+import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.FORCE_PERSISTENT_TASKBAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
@@ -41,6 +43,7 @@
 import android.view.InsetsController;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -101,11 +104,20 @@
 
     /**
      * How long to stash/unstash when manually invoked via long press.
+     *
+     * Use {@link #getStashDuration()} to query duration
      */
-    public static final long TASKBAR_STASH_DURATION =
+    private static final long TASKBAR_STASH_DURATION =
             InsetsController.ANIMATION_DURATION_RESIZE;
 
     /**
+     * How long to stash/unstash transient taskbar.
+     *
+     * Use {@link #getStashDuration()} to query duration.
+     */
+    private static final long TRANSIENT_TASKBAR_STASH_DURATION = 417;
+
+    /**
      * How long to stash/unstash when keyboard is appearing/disappearing.
      */
     private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
@@ -113,7 +125,7 @@
     /**
      * The scale TaskbarView animates to when being stashed.
      */
-    private static final float STASHED_TASKBAR_SCALE = 0.3f;
+    protected static final float STASHED_TASKBAR_SCALE = 0.5f;
 
     /**
      * How long the hint animation plays, starting on motion down.
@@ -122,6 +134,21 @@
             ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT;
 
     /**
+     * How long to delay the icon/stash handle alpha.
+     */
+    private static final long TASKBAR_STASH_ALPHA_START_DELAY = 33;
+
+    /**
+     * How long the icon/stash handle alpha animation plays.
+     */
+    private static final long TASKBAR_STASH_ALPHA_DURATION = 50;
+
+    /**
+     * How long to delay the icon/stash handle alpha for the home to app taskbar animation.
+     */
+    private static final long TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY = 66;
+
+    /**
      * The scale that TaskbarView animates to when hinting towards the stashed state.
      */
     private static final float STASHED_TASKBAR_HINT_SCALE = 0.9f;
@@ -299,7 +326,16 @@
         boolean hideTaskbar = isVisible || !mActivity.isUserSetupComplete();
         updateStateForFlag(FLAG_IN_SETUP, hideTaskbar);
         updateStateForFlag(FLAG_STASHED_IN_APP_SETUP, hideTaskbar);
-        applyState(hideTaskbar ? 0 : TASKBAR_STASH_DURATION);
+        applyState(hideTaskbar ? 0 : getStashDuration());
+    }
+
+    /**
+     * Returns how long the stash/unstash animation should play.
+     */
+    public long getStashDuration() {
+        return DisplayController.isTransientTaskbar(mActivity)
+                ? TRANSIENT_TASKBAR_STASH_DURATION
+                : TASKBAR_STASH_DURATION;
     }
 
     /**
@@ -514,7 +550,10 @@
         }
         mAnimator = new AnimatorSet();
         addJankMonitorListener(mAnimator, /* appearing= */ !mIsStashed);
-        final float stashTranslation = isPhoneMode() ? 0 : (mUnstashedHeight - mStashedHeight);
+        boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
+        final float stashTranslation = isPhoneMode() || isTransientTaskbar
+                ? 0
+                : (mUnstashedHeight - mStashedHeight);
 
         if (!supportsVisualStashing()) {
             // Just hide/show the icons and background instead of stashing into a handle.
@@ -522,8 +561,8 @@
                     .setDuration(duration));
             mAnimator.playTogether(mTaskbarBackgroundOffset.animateToValue(isStashed ? 1 : 0)
                     .setDuration(duration));
-            mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed ?
-                            stashTranslation : 0)
+            mAnimator.playTogether(mIconTranslationYForStash.animateToValue(isStashed
+                            ? stashTranslation : 0)
                     .setDuration(duration));
             mAnimator.play(mTaskbarImeBgAlpha.animateToValue(
                     hasAnyFlag(FLAG_STASHED_IN_APP_IME) ? 0 : 1).setDuration(duration));
@@ -531,6 +570,40 @@
             return;
         }
 
+        // If Hotseat is not the top element during animation to/from Launcher, fade in/out a
+        // already stashed Taskbar.
+        boolean skipStashAnimation = !mControllers.uiController.isHotseatIconOnTopWhenAligned()
+                && hasAnyFlag(changedFlags, FLAG_IN_APP);
+        if (isTransientTaskbar) {
+            createTransientAnimToIsStashed(mAnimator, isStashed, duration, animateBg, changedFlags,
+                    skipStashAnimation);
+        } else {
+            createAnimToIsStashed(mAnimator, isStashed, duration, animateBg, skipStashAnimation,
+                    stashTranslation);
+        }
+
+        mAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mIsStashed = isStashed;
+                onIsStashedChanged(mIsStashed);
+
+                cancelTimeoutIfExists();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimator = null;
+
+                if (!mIsStashed) {
+                    tryStartTaskbarTimeout();
+                }
+            }
+        });
+    }
+
+    private void createAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+            boolean animateBg, boolean skipStashAnimation, float stashTranslation) {
         AnimatorSet fullLengthAnimatorSet = new AnimatorSet();
         // Not exactly half and may overlap. See [first|second]HalfDurationScale below.
         AnimatorSet firstHalfAnimatorSet = new AnimatorSet();
@@ -539,10 +612,6 @@
         final float firstHalfDurationScale;
         final float secondHalfDurationScale;
 
-        // If Hotseat is not the top element during animation to/from Launcher, fade in/out a
-        // already stashed Taskbar.
-        boolean skipStashAnimation = !mControllers.uiController.isHotseatIconOnTopWhenAligned()
-                && hasAnyFlag(changedFlags, FLAG_IN_APP);
         if (isStashed) {
             firstHalfDurationScale = 0.75f;
             secondHalfDurationScale = 0.5f;
@@ -595,10 +664,6 @@
             }
         }
 
-        if (DisplayController.isTransientTaskbar(mActivity)) {
-            fullLengthAnimatorSet.play(mControllers.taskbarViewController
-                    .createRevealAnimToIsStashed(isStashed));
-        }
         fullLengthAnimatorSet.play(mControllers.stashedHandleViewController
                 .createRevealAnimToIsStashed(isStashed));
         // Return the stashed handle to its default scale in case it was changed as part of the
@@ -610,26 +675,73 @@
         secondHalfAnimatorSet.setDuration((long) (duration * secondHalfDurationScale));
         secondHalfAnimatorSet.setStartDelay((long) (duration * (1 - secondHalfDurationScale)));
 
-        mAnimator.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
+        as.playTogether(fullLengthAnimatorSet, firstHalfAnimatorSet,
                 secondHalfAnimatorSet);
-        mAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mIsStashed = isStashed;
-                onIsStashedChanged(mIsStashed);
 
-                cancelTimeoutIfExists();
+    }
+
+    private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
+            boolean animateBg, int changedFlags, boolean skipStashAnimation) {
+        Interpolator skipInterpolator = null;
+
+        if (isStashed) {
+            if (animateBg) {
+                play(as, mTaskbarBackgroundOffset.animateToValue(1), 0, duration, EMPHASIZED);
+            } else {
+                as.addListener(AnimatorListeners.forEndCallback(
+                        () -> mTaskbarBackgroundOffset.updateValue(1)));
             }
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mAnimator = null;
+            long alphaStartDelay = duration == 0 ? 0 : (changedFlags == FLAG_IN_APP)
+                    ? TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY
+                    : TASKBAR_STASH_ALPHA_START_DELAY;
+            long alphaDuration = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_DURATION;
+            play(as, mIconAlphaForStash.animateToValue(0), alphaStartDelay, alphaDuration, LINEAR);
+            play(as, mTaskbarStashedHandleAlpha.animateToValue(1), alphaStartDelay,
+                    Math.max(0, duration - alphaStartDelay), LINEAR);
 
-                if (!mIsStashed) {
-                    tryStartTaskbarTimeout();
-                }
+            if (skipStashAnimation) {
+                skipInterpolator = INSTANT;
             }
-        });
+        } else  {
+            if (animateBg) {
+                play(as, mTaskbarBackgroundOffset.animateToValue(0), 0, duration, EMPHASIZED);
+            } else {
+                as.addListener(AnimatorListeners.forEndCallback(
+                        () -> mTaskbarBackgroundOffset.updateValue(0)));
+            }
+
+            long alphaStartDelay = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_START_DELAY;
+            long alphaDuration = duration == 0 ? 0 : TASKBAR_STASH_ALPHA_DURATION;
+            play(as, mIconAlphaForStash.animateToValue(1), alphaStartDelay, alphaDuration, LINEAR);
+            play(as, mTaskbarStashedHandleAlpha.animateToValue(0), 0, alphaDuration, LINEAR);
+
+            if (skipStashAnimation) {
+                skipInterpolator = FINAL_FRAME;
+            }
+        }
+        play(as, mControllers.taskbarViewController
+                .createRevealAnimToIsStashed(isStashed, isInApp()), 0, duration, EMPHASIZED);
+
+        if (skipInterpolator != null) {
+            as.setInterpolator(skipInterpolator);
+        }
+
+        play(as, mControllers.stashedHandleViewController
+                .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED);
+
+        // Return the stashed handle to its default scale in case it was changed as part of the
+        // feedforward hint. Note that the reveal animation above also visually scales it.
+        as.play(mTaskbarStashedHandleHintScale.animateToValue(1f)
+                .setDuration(isStashed ? duration / 2 : duration));
+    }
+
+    private static void play(AnimatorSet as, Animator a, long startDelay, long duration,
+            Interpolator interpolator) {
+        a.setDuration(duration);
+        a.setStartDelay(startDelay);
+        a.setInterpolator(interpolator);
+        as.play(a);
     }
 
     private void addJankMonitorListener(AnimatorSet animator, boolean expanding) {
@@ -711,7 +823,6 @@
         }
     }
 
-
     /**
      * Returns an animator which applies the latest state if mIsStashed is changed, or {@code null}
      * otherwise.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 76b8b6d..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;
@@ -75,6 +76,11 @@
 
     protected void onStashedInAppChanged() { }
 
+    /**
+     * Called when taskbar icon layout bounds change.
+     */
+    protected void onIconLayoutBoundsChanged() { }
+
     /** Called when an icon is launched. */
     @CallSuper
     public void onTaskbarIconLaunched(ItemInfo item) {
@@ -183,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;
@@ -200,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
@@ -248,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
@@ -275,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 eddc278..c1e85aa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -58,6 +58,8 @@
     private static final String TAG = TaskbarView.class.getSimpleName();
 
     private static final float TASKBAR_BACKGROUND_LUMINANCE = 0.30f;
+    private static final Rect sTmpRect = new Rect();
+
     public int mThemeIconsBackground;
 
     private final int[] mTempOutLocation = new int[2];
@@ -74,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;
 
@@ -339,6 +338,9 @@
                     (right - navSpaceNeeded) - iconEnd;
             iconEnd += offset;
         }
+
+        sTmpRect.set(mIconLayoutBounds);
+
         // Layout the children
         mIconLayoutBounds.right = iconEnd;
         mIconLayoutBounds.top = (bottom - top - mIconTouchSize) / 2;
@@ -379,14 +381,10 @@
             mIconLayoutBounds.right = center + distanceFromCenter;
             mIconLayoutBounds.left = center - distanceFromCenter;
         }
-    }
 
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (!mTouchEnabled) {
-            return true;
+        if (!sTmpRect.equals(mIconLayoutBounds)) {
+            mControllerCallbacks.notifyIconLayoutBoundsChanged();
         }
-        return super.dispatchTouchEvent(ev);
     }
 
     @Override
@@ -397,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)) {
@@ -420,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 6252e60..c708838 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -17,9 +17,9 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.AnimatedFloat.VALUE;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
@@ -28,6 +28,7 @@
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
 
 import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.graphics.Rect;
@@ -199,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) {
@@ -310,13 +303,48 @@
      * @param isStashed When true, the icon crops vertically to the size of the stashed handle.
      *                  When false, the reverse happens.
      */
-    public AnimatorSet createRevealAnimToIsStashed(boolean isStashed) {
+    public AnimatorSet createRevealAnimToIsStashed(boolean isStashed, boolean isInApp) {
         AnimatorSet as = new AnimatorSet();
-        for (int i = mTaskbarView.getChildCount() - 1; i >= 0; i--) {
+
+        Rect stashedBounds = new Rect();
+        mControllers.stashedHandleViewController.getStashedHandleBounds(stashedBounds);
+
+        int numChildren = mTaskbarView.getChildCount();
+        // We do not actually modify the width of the icons, but we will use this width to position
+        // the children to overlay the nav handle.
+        float virtualChildWidth = stashedBounds.width() / (float) numChildren;
+
+        for (int i = numChildren - 1; i >= 0; i--) {
             View child = mTaskbarView.getChildAt(i);
-            if (child instanceof BubbleTextView) {
-                as.play(createRevealAnimForView(child, isStashed));
+
+            // Crop the icons to/from the nav handle shape.
+            as.play(createRevealAnimForView(child, isStashed));
+
+            // Translate the icons to/from their locations as the "nav handle."
+            // We look at 'left' and 'right' values to ensure that the children stay within the
+            // bounds of the stashed handle.
+            float iconLeft = child.getLeft();
+            float newLeft = stashedBounds.left + (virtualChildWidth * i);
+            final float croppedTransX;
+            if (iconLeft > newLeft) {
+                float newRight = stashedBounds.right - (virtualChildWidth * (numChildren - 1 - i));
+                croppedTransX = -(child.getLeft() + child.getWidth() - newRight);
+            } else {
+                croppedTransX = newLeft - iconLeft;
             }
+
+            as.play(ObjectAnimator.ofFloat(child, ICON_REVEAL_TRANSLATE_X, isStashed
+                    ? new float[] {croppedTransX}
+                    : new float[] {croppedTransX, 0}));
+
+            float croppedTransY = child.getHeight() - stashedBounds.height();
+            as.play(ObjectAnimator.ofFloat(child, ICON_REVEAL_TRANSLATE_Y, isStashed
+                    ? new float[] {croppedTransY}
+                    : new float[] {croppedTransY, 0}));
+            as.addListener(forEndCallback(() -> {
+                ICON_REVEAL_TRANSLATE_X.set(child, 0f);
+                ICON_REVEAL_TRANSLATE_Y.set(child, 0f);
+            }));
         }
         return as;
     }
@@ -409,7 +437,7 @@
                 float scale = ((float) taskbarDp.iconSizePx) / launcherDp.hotseatQsbVisualHeight;
                 setter.addFloat(child, SCALE_PROPERTY, scale, 1f, interpolator);
 
-                setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+                setter.setFloat(child, ICON_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
 
                 if (mIsHotseatIconOnTopWhenAligned) {
                     setter.addFloat(child, VIEW_ALPHA, 0f, 1f,
@@ -448,7 +476,7 @@
             float childCenter = (child.getLeft() + child.getRight()) / 2f;
             setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, interpolator);
 
-            setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+            setter.setFloat(child, ICON_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
 
             setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator);
         }
@@ -626,6 +654,13 @@
         public void clearTouchInProgress() {
             mTouchInProgress = false;
         }
+
+        /**
+         * Notifies launcher to update icon alignment.
+         */
+        public void notifyIconLayoutBoundsChanged() {
+            mControllers.uiController.onIconLayoutBoundsChanged();
+        }
     }
 
     public static final FloatProperty<View> ICON_TRANSLATE_X =
@@ -636,7 +671,7 @@
                     if (view instanceof BubbleTextView) {
                         ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
                     } else if (view instanceof FolderIcon) {
-                        ((FolderIcon) view).setTranslationForTaskbarAlignmentAnimation(v);
+                        ((FolderIcon) view).setTranslationXForTaskbarAlignmentAnimation(v);
                     } else {
                         view.setTranslationX(v);
                     }
@@ -653,4 +688,81 @@
                     return view.getTranslationX();
                 }
             };
+
+    public static final FloatProperty<View> ICON_TRANSLATE_Y =
+            new FloatProperty<View>("taskbarAlignmentTranslateY") {
+
+                @Override
+                public void setValue(View view, float v) {
+                    if (view instanceof BubbleTextView) {
+                        ((BubbleTextView) view).setTranslationYForTaskbarAlignmentAnimation(v);
+                    } else if (view instanceof FolderIcon) {
+                        ((FolderIcon) view).setTranslationYForTaskbarAlignmentAnimation(v);
+                    } else {
+                        view.setTranslationY(v);
+                    }
+                }
+
+                @Override
+                public Float get(View view) {
+                    if (view instanceof BubbleTextView) {
+                        return ((BubbleTextView) view)
+                                .getTranslationYForTaskbarAlignmentAnimation();
+                    } else if (view instanceof FolderIcon) {
+                        return ((FolderIcon) view).getTranslationYForTaskbarAlignmentAnimation();
+                    }
+                    return view.getTranslationY();
+                }
+            };
+
+    public static final FloatProperty<View> ICON_REVEAL_TRANSLATE_X =
+            new FloatProperty<View>("taskbarRevealTranslateX") {
+
+                @Override
+                public void setValue(View view, float v) {
+                    if (view instanceof BubbleTextView) {
+                        ((BubbleTextView) view).setTranslationXForTaskbarRevealAnimation(v);
+                    } else if (view instanceof FolderIcon) {
+                        ((FolderIcon) view).setTranslationXForTaskbarRevealAnimation(v);
+                    } else {
+                        view.setTranslationX(v);
+                    }
+                }
+
+                @Override
+                public Float get(View view) {
+                    if (view instanceof BubbleTextView) {
+                        return ((BubbleTextView) view).getTranslationXForTaskbarRevealAnimation();
+                    } else if (view instanceof FolderIcon) {
+                        return ((FolderIcon) view).getTranslationXForTaskbarRevealAnimation();
+                    }
+                    return view.getTranslationX();
+                }
+            };
+
+    public static final FloatProperty<View> ICON_REVEAL_TRANSLATE_Y =
+            new FloatProperty<View>("taskbarRevealTranslateY") {
+
+                @Override
+                public void setValue(View view, float v) {
+                    if (view instanceof BubbleTextView) {
+                        ((BubbleTextView) view).setTranslationYForTaskbarRevealAnimation(v);
+                    } else if (view instanceof FolderIcon) {
+                        ((FolderIcon) view).setTranslationYForTaskbarRevealAnimation(v);
+                    } else {
+                        view.setTranslationY(v);
+                    }
+                }
+
+                @Override
+                public Float get(View view) {
+                    if (view instanceof BubbleTextView) {
+                        return ((BubbleTextView) view).getTranslationYForTaskbarRevealAnimation();
+                    } else if (view instanceof FolderIcon) {
+                        return ((FolderIcon) view).getTranslationYForTaskbarRevealAnimation();
+                    }
+                    return view.getTranslationY();
+                }
+            };
+
 }
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/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index d0fd65f..ac5b2f2 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -185,6 +186,11 @@
                     && dp != null
                     && (dp.isTablet || dp.isTwoPanels);
 
+            if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+                // TODO(b/268075592): add support for quickswitch to/from desktop
+                allowQuickSwitch = false;
+            }
+
             if (cmd.type == TYPE_HIDE) {
                 if (!allowQuickSwitch) {
                     return true;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 1f522c1..9a23557 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,6 +17,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
@@ -51,8 +52,10 @@
 import android.graphics.Region;
 import android.inputmethodservice.InputMethodService;
 import android.net.Uri;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.view.MotionEvent;
 
@@ -62,9 +65,9 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -108,6 +111,15 @@
     private final boolean mIsOneHandedModeSupported;
     private boolean mPipIsActive;
 
+    private boolean mIsUserUnlocked;
+    private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
+    private final SimpleBroadcastReceiver mUserUnlockedReceiver = new SimpleBroadcastReceiver(i -> {
+        if (ACTION_USER_UNLOCKED.equals(i.getAction())) {
+            mIsUserUnlocked = true;
+            notifyUserUnlocked();
+        }
+    });
+
     private int mGestureBlockingTaskId = -1;
     private @NonNull Region mExclusionRegion = new Region();
     private SystemGestureExclusionListenerCompat mExclusionListener;
@@ -133,6 +145,14 @@
             runOnDestroy(mRotationTouchHelper::destroy);
         }
 
+        // Register for user unlocked if necessary
+        mIsUserUnlocked = context.getSystemService(UserManager.class)
+                .isUserUnlocked(Process.myUserHandle());
+        if (!mIsUserUnlocked) {
+            mUserUnlockedReceiver.register(mContext, ACTION_USER_UNLOCKED);
+        }
+        runOnDestroy(() -> mUserUnlockedReceiver.unregisterReceiverSafely(mContext));
+
         // Register for exclusion updates
         mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
             @Override
@@ -292,12 +312,39 @@
     }
 
     /**
+     * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
+     * will be called back immediately.
+     */
+    public void runOnUserUnlocked(Runnable action) {
+        if (mIsUserUnlocked) {
+            action.run();
+        } else {
+            mUserUnlockedActions.add(action);
+        }
+    }
+
+    /**
+     * @return whether the user is unlocked.
+     */
+    public boolean isUserUnlocked() {
+        return mIsUserUnlocked;
+    }
+
+    /**
      * @return whether the user has completed setup wizard
      */
     public boolean isUserSetupComplete() {
         return mIsUserSetupComplete;
     }
 
+    private void notifyUserUnlocked() {
+        for (Runnable action : mUserUnlockedActions) {
+            action.run();
+        }
+        mUserUnlockedActions.clear();
+        mUserUnlockedReceiver.unregisterReceiverSafely(mContext);
+    }
+
     /**
      * Sets the task id where gestures should be blocked
      */
@@ -542,7 +589,7 @@
         pw.println("  assistantAvailable=" + mAssistantAvailable);
         pw.println("  assistantDisabled="
                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
-        pw.println("  isUserUnlocked=" + LockedUserState.get(mContext).isUserUnlocked());
+        pw.println("  isUserUnlocked=" + mIsUserUnlocked);
         pw.println("  isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
         pw.println("  isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
         pw.println("  deferredGestureRegion=" + mDeferredGestureRegion.getBounds());
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/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1b8a93c..61caef2 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -88,7 +88,6 @@
 import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
@@ -412,8 +411,8 @@
         mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
 
         // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
-        LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
-        LockedUserState.get(this).runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
+        mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
+        mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
 
         ProtoTracer.INSTANCE.get(this).add(this);
@@ -483,7 +482,7 @@
     }
 
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
-        if (!LockedUserState.get(this).isUserUnlocked() || mDeviceState.isButtonNavMode()) {
+        if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
             // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
             // mode doesn't have gestures
             return;
@@ -526,7 +525,7 @@
 
     @UiThread
     private void onSystemUiFlagsChanged(int lastSysUIFlags) {
-        if (LockedUserState.get(this).isUserUnlocked()) {
+        if (mDeviceState.isUserUnlocked()) {
             int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
             SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
             mOverviewComponentObserver.onSystemUiStateChanged();
@@ -571,7 +570,7 @@
 
     @UiThread
     private void onAssistantVisibilityChanged() {
-        if (LockedUserState.get(this).isUserUnlocked()) {
+        if (mDeviceState.isUserUnlocked()) {
             mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
                     mDeviceState.getAssistantVisibility());
         }
@@ -581,7 +580,7 @@
     public void onDestroy() {
         Log.d(TAG, "Touch service destroyed: user=" + getUserId());
         sIsInitialized = false;
-        if (LockedUserState.get(this).isUserUnlocked()) {
+        if (mDeviceState.isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
             mOverviewComponentObserver.onDestroy();
         }
@@ -615,7 +614,7 @@
         TestLogging.recordMotionEvent(
                 TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
 
-        if (!LockedUserState.get(this).isUserUnlocked()) {
+        if (!mDeviceState.isUserUnlocked()) {
             return;
         }
 
@@ -637,8 +636,7 @@
                 mGestureState = newGestureState;
                 mConsumer = newConsumer(prevGestureState, mGestureState, event);
                 mUncheckedConsumer = mConsumer;
-            } else if (LockedUserState.get(this).isUserUnlocked()
-                    && mDeviceState.isFullyGesturalNavMode()
+            } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()
                     && mDeviceState.canTriggerAssistantAction(event)) {
                 mGestureState = createGestureState(mGestureState);
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
@@ -758,7 +756,7 @@
 
         boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
 
-        if (!LockedUserState.get(this).isUserUnlocked()) {
+        if (!mDeviceState.isUserUnlocked()) {
             CompoundString reasonString = newCompoundString("device locked");
             InputConsumer consumer;
             if (canStartSystemGesture) {
@@ -1105,7 +1103,7 @@
     }
 
     private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
-        if (!LockedUserState.get(this).isUserUnlocked()) {
+        if (!mDeviceState.isUserUnlocked()) {
             return;
         }
 
@@ -1137,7 +1135,7 @@
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
-        if (!LockedUserState.get(this).isUserUnlocked()) {
+        if (!mDeviceState.isUserUnlocked()) {
             return;
         }
         final BaseActivityInterface activityInterface =
@@ -1178,7 +1176,7 @@
         } else {
             // Dump everything
             FeatureFlags.dump(pw);
-            if (LockedUserState.get(this).isUserUnlocked()) {
+            if (mDeviceState.isUserUnlocked()) {
                 PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
             }
             mDeviceState.dump(pw);
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/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 0e2f020..fb85605 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -431,9 +431,12 @@
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
 
-        setWillNotDraw(!FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get());
+        boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
+                || DesktopTaskView.DESKTOP_MODE_SUPPORTED;
 
-        mBorderAnimator = !FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
+        setWillNotDraw(!keyboardFocusHighlightEnabled);
+
+        mBorderAnimator = !keyboardFocusHighlightEnabled
                 ? null
                 : new BorderAnimator(
                         /* borderBoundsBuilder= */ this::updateBorderBounds,
@@ -1442,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 edbce10..9e28608 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -104,6 +104,10 @@
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
 
     private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+    private float mTranslationYForTaskbarAlignmentAnimation = 0f;
+
+    private float mTranslationXForTaskbarRevealAnimation = 0f;
+    private float mTranslationYForTaskbarRevealAnimation = 0f;
 
     private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
 
@@ -370,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;
         }
@@ -384,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);
@@ -952,11 +959,17 @@
     }
 
     private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+        super.setTranslationX(mTranslationForReorderBounce.x
+                + mTranslationForReorderPreview.x
                 + mTranslationForMoveFromCenterAnimation.x
-                + mTranslationXForTaskbarAlignmentAnimation);
-        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
-                + mTranslationForMoveFromCenterAnimation.y);
+                + mTranslationXForTaskbarAlignmentAnimation
+                + mTranslationXForTaskbarRevealAnimation
+        );
+        super.setTranslationY(mTranslationForReorderBounce.y
+                + mTranslationForReorderPreview.y
+                + mTranslationForMoveFromCenterAnimation.y
+                + mTranslationYForTaskbarAlignmentAnimation
+                + mTranslationYForTaskbarRevealAnimation);
     }
 
     public void setReorderBounceOffset(float x, float y) {
@@ -1012,6 +1025,51 @@
         return mTranslationXForTaskbarAlignmentAnimation;
     }
 
+    /**
+     * Sets translationX for taskbar to launcher alignment animation
+     */
+    public void setTranslationYForTaskbarAlignmentAnimation(float translationY) {
+        mTranslationYForTaskbarAlignmentAnimation = translationY;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translationY value for taskbar to launcher alignment animation
+     */
+    public float getTranslationYForTaskbarAlignmentAnimation() {
+        return mTranslationYForTaskbarAlignmentAnimation;
+    }
+
+    /**
+     * Sets translationX value for taskbar reveal animation
+     */
+    public void setTranslationXForTaskbarRevealAnimation(float translationX) {
+        mTranslationXForTaskbarRevealAnimation = translationX;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translation values for taskbar reveal animation
+     */
+    public float getTranslationXForTaskbarRevealAnimation() {
+        return mTranslationXForTaskbarRevealAnimation;
+    }
+
+    /**
+     * Sets translationY value for taskbar reveal animation
+     */
+    public void setTranslationYForTaskbarRevealAnimation(float translationY) {
+        mTranslationYForTaskbarRevealAnimation = translationY;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translationY values for taskbar reveal animation
+     */
+    public float getTranslationYForTaskbarRevealAnimation() {
+        return mTranslationYForTaskbarRevealAnimation;
+    }
+
     public View getView() {
         return this;
     }
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/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/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8097fd7..8e53101 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1947,7 +1947,7 @@
         Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
         setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: processShortcutFromDrop");
-        if (!info.activityInfo.startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
+        if (!info.getActivityInfo(this).startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
             handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
         }
     }
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/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java
index b7a22fc..000ddd8 100644
--- a/src/com/android/launcher3/PendingAddItemInfo.java
+++ b/src/com/android/launcher3/PendingAddItemInfo.java
@@ -21,7 +21,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
 
 import java.util.Optional;
 
@@ -29,13 +29,20 @@
  * Meta data that is used for deferred binding. e.g., this object is used to pass information on
  * draggable targets when they are dropped onto the workspace from another container.
  */
-public class PendingAddItemInfo extends ItemInfo {
+public class PendingAddItemInfo extends ItemInfoWithIcon {
 
     /**
      * The component that will be created.
      */
     public ComponentName componentName;
 
+    public PendingAddItemInfo() { }
+
+    public PendingAddItemInfo(PendingAddItemInfo info) {
+        super(info);
+        componentName = info.componentName;
+    }
+
     @Override
     protected String dumpProperties() {
         return super.dumpProperties() + " componentName=" + componentName;
@@ -46,13 +53,18 @@
      */
     @NonNull
     @Override
-    public ItemInfo makeShallowCopy() {
+    public PendingAddItemInfo makeShallowCopy() {
         PendingAddItemInfo itemInfo = new PendingAddItemInfo();
         itemInfo.copyFrom(this);
         itemInfo.componentName = this.componentName;
         return itemInfo;
     }
 
+    @Override
+    public PendingAddItemInfo clone() {
+        return makeShallowCopy();
+    }
+
     @Nullable
     @Override
     public ComponentName getTargetComponent() {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index bd9493b..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.
@@ -557,6 +573,12 @@
             int width, int height, Object[] outObj) {
         ActivityContext activity = ActivityContext.lookupContext(context);
         LauncherAppState appState = LauncherAppState.getInstance(context);
+        if (info instanceof PendingAddShortcutInfo) {
+            ShortcutConfigActivityInfo activityInfo =
+                    ((PendingAddShortcutInfo) info).getActivityInfo(context);
+            outObj[0] = activityInfo;
+            return activityInfo.getFullResIcon(appState.getIconCache());
+        }
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
             LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class)
                     .resolveActivity(info.getIntent(), info.user);
@@ -565,12 +587,6 @@
                     .getIconProvider().getIcon(
                             activityInfo, activity.getDeviceProfile().inv.fillResIconDpi);
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
-            if (info instanceof PendingAddShortcutInfo) {
-                ShortcutConfigActivityInfo activityInfo =
-                        ((PendingAddShortcutInfo) info).activityInfo;
-                outObj[0] = activityInfo;
-                return activityInfo.getFullResIcon(appState.getIconCache());
-            }
             List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
                     .buildRequest(context)
                     .query(ShortcutRequest.ALL);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index cfb8ca4..ba492d5 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2690,7 +2690,7 @@
     private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) {
         if (d.dragInfo instanceof PendingAddShortcutInfo) {
             WorkspaceItemInfo si = ((PendingAddShortcutInfo) d.dragInfo)
-                    .activityInfo.createWorkspaceItemInfo();
+                    .getActivityInfo(mLauncher).createWorkspaceItemInfo();
             if (si != null) {
                 d.dragInfo = si;
             }
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 3e98cd9..8fbe997 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -365,6 +365,9 @@
                 mAH.get(i).mRecyclerView.scrollToTop();
             }
         }
+        if (mTouchHandler != null) {
+            mTouchHandler.endFastScrolling();
+        }
         if (mHeader != null && mHeader.getVisibility() == VISIBLE) {
             mHeader.reset(animate);
         }
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 f54d05d..46c8e81 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -56,7 +56,6 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
@@ -84,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];
 
@@ -159,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);
@@ -170,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();
             }
@@ -218,12 +218,6 @@
      */
     @TargetApi(Build.VERSION_CODES.O)
     public void setItemInfo(final ItemInfo info) {
-        if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
-                && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION
-                && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
-                && info.itemType != LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-            return;
-        }
         // Load the adaptive icon on a background thread and add the view in ui thread.
         MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
             Object[] outObj = new Object[1];
@@ -515,6 +509,10 @@
         return mInitialScale;
     }
 
+    public float getEndScale() {
+        return mEndScale;
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         return false;
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index f9916d0..6b21522 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -44,7 +44,7 @@
  * request.
  */
 @TargetApi(Build.VERSION_CODES.O)
-class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
+public class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo {
 
     // Class name used in the target component, such that it will never represent an
     // actual existing class.
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 9a5d77e..c9fe745 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1263,7 +1263,7 @@
         PendingAddShortcutInfo pasi = d.dragInfo instanceof PendingAddShortcutInfo
                 ? (PendingAddShortcutInfo) d.dragInfo : null;
         WorkspaceItemInfo pasiSi =
-                pasi != null ? pasi.activityInfo.createWorkspaceItemInfo() : null;
+                pasi != null ? pasi.getActivityInfo(launcher).createWorkspaceItemInfo() : null;
         if (pasi != null && pasiSi == null) {
             // There is no WorkspaceItemInfo, so we have to go through a configuration activity.
             pasi.container = mInfo.id;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index e69f781..ee1a060 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -135,6 +135,9 @@
 
     private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
     private float mTranslationXForTaskbarAlignmentAnimation = 0f;
+    private float mTranslationYForTaskbarAlignmentAnimation = 0f;
+    private float mTranslationXForTaskbarRevealAnimation = 0f;
+    private float mTranslationYForTaskbarRevealAnimation = 0f;
 
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -416,7 +419,7 @@
                     () -> {
                         mPreviewItemManager.hidePreviewItem(finalIndex, false);
                         mFolder.showItem(item);
-                    }, 
+                    },
                     DragLayer.ANIMATION_END_DISAPPEAR, null);
 
             mFolder.hideItem(item);
@@ -768,11 +771,15 @@
     }
 
     private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
+        super.setTranslationX(mTranslationForReorderBounce.x
+                + mTranslationForReorderPreview.x
                 + mTranslationForMoveFromCenterAnimation.x
-                + mTranslationXForTaskbarAlignmentAnimation);
+                + mTranslationXForTaskbarAlignmentAnimation
+                + mTranslationXForTaskbarRevealAnimation);
         super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
-                + mTranslationForMoveFromCenterAnimation.y);
+                + mTranslationForMoveFromCenterAnimation.y
+                + mTranslationYForTaskbarAlignmentAnimation
+                + mTranslationYForTaskbarRevealAnimation);
     }
 
     public void setReorderBounceOffset(float x, float y) {
@@ -787,7 +794,7 @@
     /**
      * Sets translationX value for taskbar to launcher alignment animation
      */
-    public void setTranslationForTaskbarAlignmentAnimation(float translationX) {
+    public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
         mTranslationXForTaskbarAlignmentAnimation = translationX;
         updateTranslation();
     }
@@ -800,6 +807,51 @@
     }
 
     /**
+     * Sets translationY value for taskbar to launcher alignment animation
+     */
+    public void setTranslationYForTaskbarAlignmentAnimation(float translationY) {
+        mTranslationYForTaskbarAlignmentAnimation = translationY;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translation values for taskbar to launcher alignment animation
+     */
+    public float getTranslationYForTaskbarAlignmentAnimation() {
+        return mTranslationYForTaskbarAlignmentAnimation;
+    }
+
+    /**
+     * Sets translationX value for taskbar reveal animation
+     */
+    public void setTranslationXForTaskbarRevealAnimation(float translationX) {
+        mTranslationXForTaskbarRevealAnimation = translationX;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translation values for taskbar reveal animation
+     */
+    public float getTranslationXForTaskbarRevealAnimation() {
+        return mTranslationXForTaskbarRevealAnimation;
+    }
+
+    /**
+     * Sets translationY value for taskbar reveal animation
+     */
+    public void setTranslationYForTaskbarRevealAnimation(float translationY) {
+        mTranslationYForTaskbarRevealAnimation = translationY;
+        updateTranslation();
+    }
+
+    /**
+     * Returns translationY values for taskbar reveal animation
+     */
+    public float getTranslationYForTaskbarRevealAnimation() {
+        return mTranslationYForTaskbarRevealAnimation;
+    }
+
+    /**
      * Sets translation values for move from center animation
      */
     public void setTranslationForMoveFromCenterAnimation(float x, float y) {
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 9426c22..2c8f1f3 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -138,12 +138,14 @@
                 }
 
                 idp.setCurrentGrid(getContext(), gridName);
+                getContext().getContentResolver().notifyChange(uri, null);
                 return 1;
             }
             case ICON_THEMED:
             case SET_ICON_THEMED: {
                 LauncherPrefs.get(getContext())
                         .put(THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE));
+                getContext().getContentResolver().notifyChange(uri, null);
                 return 1;
             }
             default:
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 0b4a4a5..3c63f26 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -60,6 +60,7 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.InstantAppResolver;
@@ -81,6 +82,11 @@
  */
 public class IconCache extends BaseIconCache {
 
+    // Shortcut extra which can point to a packageName and can be used to indicate an alternate
+    // badge info. Launcher only reads this if the shortcut comes from a system app.
+    public static final String EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE =
+            "extra_shortcut_badge_override_package";
+
     private static final String TAG = "Launcher.IconCache";
 
     private final Predicate<ItemInfoWithIcon> mIsUsingFallbackOrNonDefaultIconCheck = w ->
@@ -260,8 +266,15 @@
             getTitleAndIcon(appInfo, false);
             return appInfo.bitmap;
         } else {
-            PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage(),
-                    shortcutInfo.getUserHandle());
+            String pkg = shortcutInfo.getPackage();
+            String override = shortcutInfo.getExtras() == null ? null
+                    : shortcutInfo.getExtras().getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE);
+            if (!TextUtils.isEmpty(override)
+                    && InstallSessionHelper.INSTANCE.get(mContext)
+                            .isTrustedPackage(pkg, shortcutInfo.getUserHandle())) {
+                pkg = override;
+            }
+            PackageItemInfo pkgInfo = new PackageItemInfo(pkg, shortcutInfo.getUserHandle());
             getTitleAndIconForApp(pkgInfo, false);
             return pkgInfo.bitmap;
         }
@@ -484,8 +497,7 @@
         WidgetSection widgetSection = WidgetSections.getWidgetSections(mContext)
                 .get(infoInOut.widgetCategory);
         infoInOut.title = mContext.getString(widgetSection.mSectionTitle);
-        infoInOut.contentDescription = mPackageManager.getUserBadgedLabel(
-                infoInOut.title, infoInOut.user);
+        infoInOut.contentDescription = getUserBadgedLabel(infoInOut.title, infoInOut.user);
         final BitmapInfo cachedBitmap = mWidgetCategoryBitmapInfos.get(infoInOut.widgetCategory);
         if (cachedBitmap != null) {
             infoInOut.bitmap = getBadgedIcon(cachedBitmap, infoInOut.user);
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 0a6a7cd..855a69d 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -310,7 +310,7 @@
             throw new InvalidParameterException("Invalid restoreType " + restoreFlag);
         }
 
-        info.contentDescription = mPM.getUserBadgedLabel(info.title, info.user);
+        info.contentDescription = mIconCache.getUserBadgedLabel(info.title, info.user);
         info.itemType = itemType;
         info.status = restoreFlag;
         return info;
@@ -381,7 +381,7 @@
             }
         }
 
-        info.contentDescription = mPM.getUserBadgedLabel(info.title, info.user);
+        info.contentDescription = mIconCache.getUserBadgedLabel(info.title, info.user);
         return info;
     }
 
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index db23566..7ca3b11 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -171,15 +171,22 @@
             }
             return null;
         }
-        String pkg = sessionInfo.getInstallerPackageName();
+        return isTrustedPackage(sessionInfo.getInstallerPackageName(), getUserHandle(sessionInfo))
+                ? sessionInfo : null;
+    }
+
+    /**
+     * Returns true if the provided packageName can be trusted for user configurations
+     */
+    public boolean isTrustedPackage(String pkg, UserHandle user) {
         synchronized (mSessionVerifiedMap) {
             if (!mSessionVerifiedMap.containsKey(pkg)) {
                 boolean hasSystemFlag = new PackageManagerHelper(mAppContext).getApplicationInfo(
-                        pkg, getUserHandle(sessionInfo), ApplicationInfo.FLAG_SYSTEM) != null;
+                        pkg, user, ApplicationInfo.FLAG_SYSTEM) != null;
                 mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag);
             }
         }
-        return mSessionVerifiedMap.get(pkg) ? sessionInfo : null;
+        return mSessionVerifiedMap.get(pkg);
     }
 
     @NonNull
diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index 7af14c6..14e67b2 100644
--- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
@@ -112,26 +111,6 @@
         return true;
     }
 
-    static class ShortcutConfigActivityInfoVL extends ShortcutConfigActivityInfo {
-
-        private final ActivityInfo mInfo;
-
-        ShortcutConfigActivityInfoVL(ActivityInfo info) {
-            super(new ComponentName(info.packageName, info.name), Process.myUserHandle());
-            mInfo = info;
-        }
-
-        @Override
-        public CharSequence getLabel(PackageManager pm) {
-            return mInfo.loadLabel(pm);
-        }
-
-        @Override
-        public Drawable getFullResIcon(IconCache cache) {
-            return cache.getFullResIcon(mInfo);
-        }
-    }
-
     @TargetApi(26)
     public static class ShortcutConfigActivityInfoVO extends ShortcutConfigActivityInfo {
 
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
deleted file mode 100644
index 7b49583..0000000
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.android.launcher3.util
-
-import android.content.Context
-import android.content.Intent
-import android.os.Process
-import android.os.UserManager
-import androidx.annotation.VisibleForTesting
-
-class LockedUserState(private val mContext: Context) : SafeCloseable {
-    var isUserUnlocked: Boolean
-        private set
-    private val mUserUnlockedActions: RunnableList = RunnableList()
-
-    @VisibleForTesting
-    val mUserUnlockedReceiver = SimpleBroadcastReceiver {
-        if (Intent.ACTION_USER_UNLOCKED == it.action) {
-            isUserUnlocked = true
-            notifyUserUnlocked()
-        }
-    }
-
-    init {
-        isUserUnlocked =
-            mContext
-                .getSystemService(UserManager::class.java)!!
-                .isUserUnlocked(Process.myUserHandle())
-        if (isUserUnlocked) {
-            notifyUserUnlocked()
-        } else {
-            mUserUnlockedReceiver.register(mContext, Intent.ACTION_USER_UNLOCKED)
-        }
-    }
-
-    private fun notifyUserUnlocked() {
-        mUserUnlockedActions.executeAllAndDestroy()
-        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
-    }
-
-    /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
-    override fun close() {
-        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
-    }
-
-    /**
-     * Adds a `Runnable` to be executed when a user is unlocked. If the user is already unlocked,
-     * this runnable will run immediately because RunnableList will already have been destroyed.
-     */
-    fun runOnUserUnlocked(action: Runnable) {
-        mUserUnlockedActions.add(action)
-    }
-
-    companion object {
-        @VisibleForTesting val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
-
-        @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
-    }
-}
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 1274294..a941833 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -281,15 +281,7 @@
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mRv.onFastScrollCompleted();
-                mTouchOffsetY = 0;
-                mLastTouchY = 0;
-                mIgnoreDragGesture = false;
-                if (mIsDragging) {
-                    mIsDragging = false;
-                    animatePopupVisibility(false);
-                    showActiveScrollbar(false);
-                }
+                endFastScrolling();
                 break;
         }
         if (DEBUG) {
@@ -328,6 +320,19 @@
         setThumbOffsetY((int) mLastTouchY);
     }
 
+    /** End any active fast scrolling touch handling, if applicable. */
+    public void endFastScrolling() {
+        mRv.onFastScrollCompleted();
+        mTouchOffsetY = 0;
+        mLastTouchY = 0;
+        mIgnoreDragGesture = false;
+        if (mIsDragging) {
+            mIsDragging = false;
+            animatePopupVisibility(false);
+            showActiveScrollbar(false);
+        }
+    }
+
     public void onDraw(Canvas canvas) {
         if (mThumbOffsetY < 0 || mRv == null) {
             return;
diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
index 9601652..3935be5 100644
--- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
+++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java
@@ -17,6 +17,8 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
 
+import android.content.Context;
+
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 
@@ -27,13 +29,28 @@
  */
 public class PendingAddShortcutInfo extends PendingAddItemInfo {
 
-    public ShortcutConfigActivityInfo activityInfo;
+    // TODO: Make it @NonNull
+    protected ShortcutConfigActivityInfo mActivityInfo;
 
     public PendingAddShortcutInfo(ShortcutConfigActivityInfo activityInfo) {
-        this.activityInfo = activityInfo;
+        this.mActivityInfo = activityInfo;
         componentName = activityInfo.getComponent();
         user = activityInfo.getUser();
         itemType = activityInfo.getItemType();
         this.container = CONTAINER_WIDGETS_TRAY;
     }
+
+    public PendingAddShortcutInfo(PendingAddShortcutInfo info) {
+        super(info);
+        mActivityInfo = info.mActivityInfo;
+    }
+
+    public PendingAddShortcutInfo() { }
+
+    /**
+     * Returns the info used for creating the shortcut
+     */
+    public ShortcutConfigActivityInfo getActivityInfo(Context context) {
+        return mActivityInfo;
+    }
 }
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index bbbc329..2dedd12 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -180,7 +180,8 @@
             draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_WIDGET);
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
-            Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
+            Drawable icon = createShortcutInfo.getActivityInfo(launcher)
+                    .getFullResIcon(app.getIconCache());
             LauncherIcons li = LauncherIcons.obtain(launcher);
             preview = new FastBitmapDrawable(
                     li.createScaledBitmap(icon, BaseIconFactory.MODE_DEFAULT));
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)
diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
deleted file mode 100644
index 84156e7..0000000
--- a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.launcher3.util
-
-import android.content.Context
-import android.content.Intent
-import android.os.Process
-import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-
-/** Unit tests for {@link LockedUserUtil} */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class LockedUserStateTest {
-
-    @Mock lateinit var userManager: UserManager
-    @Mock lateinit var context: Context
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
-    }
-
-    @Test
-    fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
-        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
-        val action: Runnable = mock()
-
-        LockedUserState.get(context).runOnUserUnlocked(action)
-        verify(action).run()
-    }
-
-    @Test
-    fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
-        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
-        val action: Runnable = mock()
-
-        LockedUserState.get(context).runOnUserUnlocked(action)
-        verifyZeroInteractions(action)
-
-        LockedUserState.get(context)
-            .mUserUnlockedReceiver
-            .onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
-
-        verify(action).run()
-    }
-
-    @Test
-    fun isUserUnlocked_returns_true_when_user_is_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
-        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
-        assertThat(LockedUserState.get(context).isUserUnlocked).isTrue()
-    }
-
-    @Test
-    fun isUserUnlocked_returns_false_when_user_is_locked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
-        LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
-        assertThat(LockedUserState.get(context).isUserUnlocked).isFalse()
-    }
-}