Merge "Add a feature flag to toggle DW carousel detach" into main
diff --git a/Android.bp b/Android.bp
index 9d7aa73..223e2c2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -66,6 +66,8 @@
     srcs: [
         "quickstep/src/**/*.kt",
         "quickstep/src/**/*.java",
+    ],
+    device_common_srcs: [
         ":launcher-quickstep-processed-protolog-src",
     ],
 }
@@ -90,7 +92,7 @@
     ],
 }
 
-genrule {
+java_genrule {
     name: "launcher-quickstep-processed-protolog-src",
     srcs: [
         ":protolog-impl",
@@ -108,7 +110,7 @@
     out: ["launcher.quickstep.protolog.srcjar"],
 }
 
-genrule {
+java_genrule {
     name: "gen-launcher.quickstep.protolog.pb",
     srcs: [
         ":launcher-quickstep-unprocessed-protolog-src",
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 29e1f4e..2f4c6f6 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -59,7 +59,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
@@ -156,9 +155,6 @@
                         state.containerId);
         FixedContainerItems fci = new FixedContainerItems(state.containerId,
                 state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
-        if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-            bindPredictionItems(callbacks, fci);
-        }
         mDataModel.extraItems.put(state.containerId, fci);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 50a253c..05d34b5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -131,6 +131,15 @@
     }
 
     @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mViewCallbacks != null) {
+            mViewCallbacks.onViewDetchedFromWindow();
+        }
+    }
+
+    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         mNoRecentItemsPane = findViewById(R.id.no_recent_items_pane);
@@ -281,6 +290,10 @@
         return mDesktopTaskIndex;
     }
 
+    void resetViewCallbacks() {
+        mViewCallbacks = null;
+    }
+
     protected Animator getCloseAnimation() {
         AnimatorSet closeAnimation = new AnimatorSet();
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 2902d55..5d77f21 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -70,6 +70,8 @@
     private boolean mOnDesktop;
     private boolean mWasDesktopTaskFilteredOut;
 
+    private boolean mDetachingFromWindow = false;
+
     protected KeyboardQuickSwitchViewController(
             @NonNull TaskbarControllers controllers,
             @NonNull TaskbarOverlayContext overlayContext,
@@ -237,7 +239,12 @@
 
     private void onCloseComplete() {
         mCloseAnimation = null;
-        mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
+        // Reset the view callbacks to prevent `onDetachedFromWindow` getting called in response to
+        // the `removeView(mKeyboardQuickSwitchView)` call.
+        mKeyboardQuickSwitchView.resetViewCallbacks();
+        if (!mDetachingFromWindow) {
+            mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
+        }
         mControllerCallbacks.onCloseComplete();
         InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
     }
@@ -327,5 +334,11 @@
         boolean isAspectRatioSquare() {
             return mControllerCallbacks.isAspectRatioSquare();
         }
+
+        void onViewDetchedFromWindow() {
+            mDetachingFromWindow = true;
+            closeQuickSwitchView(false);
+            mDetachingFromWindow = false;
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 042bc9a..09dbeb6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
@@ -69,17 +68,14 @@
     public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1;
     public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2;
     public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3;
-    public static final int LAUNCHER_PAUSE_PROGRESS_INDEX = 4;
 
-    public static final int DISPLAY_PROGRESS_COUNT = 5;
+    public static final int DISPLAY_PROGRESS_COUNT = 4;
 
     private final AnimatedFloat mTaskbarInAppDisplayProgress = new AnimatedFloat(
             this::onInAppDisplayProgressChanged);
     private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp =
             new MultiPropertyFactory<>(mTaskbarInAppDisplayProgress,
                     AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
-    private final AnimatedFloat mLauncherPauseProgress = new AnimatedFloat(
-            this::launcherPauseProgressUpdate);
 
     private final QuickstepLauncher mLauncher;
     private final HomeVisibilityState mHomeState;
@@ -195,33 +191,6 @@
     }
 
     /**
-     * Called when Launcher Activity is paused/resumed.
-     * <p>
-     * To avoid UI clash between taskbar & bottom sheet, shift nav buttons down on launcher
-     * pause/resume at home.
-     * @param paused if launcher is currently paused.
-     */
-    public void onLauncherPausedOrResumed(boolean paused) {
-        if (!FeatureFlags.enableHomeTransitionListener()) {
-            onLauncherVisibilityChanged(mLauncher.hasBeenResumed());
-            return;
-        }
-
-        // Animate navbar iff pause/resume from home, NOT to/from app (avoid overriding existing
-        // animations).
-        boolean launcherPauseOrResumeFromHome = mHomeState.isHomeVisible() && mControllers
-                .taskbarAutohideSuspendController.isSuspendedForTransientTaskbarInLauncher();
-        if (launcherPauseOrResumeFromHome) {
-            mLauncherPauseProgress.animateToValue(paused ? 1.0f : 0.0f).start();
-        }
-    }
-
-    private void launcherPauseProgressUpdate() {
-        onTaskbarInAppDisplayProgressUpdate(
-                mLauncherPauseProgress.value, LAUNCHER_PAUSE_PROGRESS_INDEX);
-    }
-
-    /**
      * Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
      */
     @Override
@@ -395,20 +364,18 @@
         }
         if (mControllers.uiController.isIconAlignedWithHotseat()
                 && !mTaskbarLauncherStateController.isAnimatingToLauncher()) {
-            // Only animate nav button position while home and not animating home, otherwise let
+            // Only animate the nav buttons while home and not animating home, otherwise let
             // the TaskbarViewController handle it.
             mControllers.navbarButtonsViewController
-                    .getNavButtonTranslationYForInAppDisplay()
+                    .getTaskbarNavButtonTranslationYForInAppDisplay()
                     .updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
                             * mTaskbarInAppDisplayProgress.value);
-            if (!mLauncher.isPaused()) {
-                mControllers.navbarButtonsViewController
-                        .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
-            }
+            mControllers.navbarButtonsViewController
+                    .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
         }
     }
 
-    @Override
+    /** Returns true iff any in-app display progress > 0. */
     public boolean shouldUseInAppLayout() {
         return mTaskbarInAppDisplayProgress.value > 0;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index cfcbd2f..7d8e93c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -183,7 +183,7 @@
 
     private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
             this::updateNavButtonTranslationY);
-    private final AnimatedFloat mNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
+    private final AnimatedFloat mTaskbarNavButtonTranslationYForInAppDisplay = new AnimatedFloat(
             this::updateNavButtonTranslationY);
     private final AnimatedFloat mTaskbarNavButtonTranslationYForIme = new AnimatedFloat(
             this::updateNavButtonTranslationY);
@@ -704,8 +704,8 @@
     }
 
     /** Use to set the translationY for the all nav+contextual buttons when in Launcher */
-    public AnimatedFloat getNavButtonTranslationYForInAppDisplay() {
-        return mNavButtonTranslationYForInAppDisplay;
+    public AnimatedFloat getTaskbarNavButtonTranslationYForInAppDisplay() {
+        return mTaskbarNavButtonTranslationYForInAppDisplay;
     }
 
     /** Use to set the dark intensity for the all nav+contextual buttons */
@@ -751,20 +751,18 @@
         if (mContext.isPhoneButtonNavMode()) {
             return;
         }
-        mLastSetNavButtonTranslationY = calculateNavButtonTranslationY();
-        mNavButtonsView.setTranslationY(mLastSetNavButtonTranslationY);
-    }
+        final float normalTranslationY = mTaskbarNavButtonTranslationY.value;
+        final float imeAdjustmentTranslationY = mTaskbarNavButtonTranslationYForIme.value;
+        TaskbarUIController uiController = mControllers.uiController;
+        final float inAppDisplayAdjustmentTranslationY =
+                (uiController instanceof LauncherTaskbarUIController
+                        && ((LauncherTaskbarUIController) uiController).shouldUseInAppLayout())
+                        ? mTaskbarNavButtonTranslationYForInAppDisplay.value : 0;
 
-    /**
-     * Calculates the translationY of the nav buttons based on the current device state.
-     */
-    private float calculateNavButtonTranslationY() {
-        float translationY =
-                mTaskbarNavButtonTranslationY.value + mTaskbarNavButtonTranslationYForIme.value;
-        if (mControllers.uiController.shouldUseInAppLayout()) {
-            translationY += mNavButtonTranslationYForInAppDisplay.value;
-        }
-        return translationY;
+        mLastSetNavButtonTranslationY = normalTranslationY
+                + imeAdjustmentTranslationY
+                + inAppDisplayAdjustmentTranslationY;
+        mNavButtonsView.setTranslationY(mLastSetNavButtonTranslationY);
     }
 
     /**
@@ -1164,7 +1162,7 @@
         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationY="
                 + mTaskbarNavButtonTranslationY.value);
         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForInAppDisplay="
-                + mNavButtonTranslationYForInAppDisplay.value);
+                + mTaskbarNavButtonTranslationYForInAppDisplay.value);
         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForIme="
                 + mTaskbarNavButtonTranslationYForIme.value);
         pw.println(prefix + "\t\tmTaskbarNavButtonDarkIntensity="
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6f1e96f..e22de06 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -29,7 +29,6 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
 import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.Flags.taskbarOverflow;
 import static com.android.launcher3.Utilities.calculateTextHeight;
 import static com.android.launcher3.Utilities.isRunningInTestHarness;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
@@ -1217,9 +1216,7 @@
         boolean shouldCloseAllOpenViews = true;
         Object tag = view.getTag();
 
-        if (taskbarOverflow()) {
-            mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
-        }
+        mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
 
         if (tag instanceof GroupTask groupTask) {
             handleGroupTaskLaunch(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 4a6b6d4..325a0d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -150,7 +150,7 @@
         }
         if (
             taskbarStashController.isInApp ||
-                taskbarStashController.isInOverview ||
+                controllers.uiController.isInOverviewUi ||
                 DisplayController.showLockedTaskbarOnHome(context)
         ) {
             // only add the taskbar touch region if not on home
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 7030088..f7f5cf6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -91,14 +91,6 @@
     protected void onStashedInAppChanged() { }
 
     /**
-     * Whether the Taskbar should use in-app layout.
-     * @return {@code true} iff in-app display progress > 0 or Launcher Activity paused.
-     */
-    public boolean shouldUseInAppLayout() {
-        return false;
-    }
-
-    /**
      * Called when taskbar icon layout bounds change.
      */
     protected void onIconLayoutBoundsChanged() { }
@@ -126,6 +118,8 @@
      * Manually closes the overlay window.
      */
     public void hideOverlayWindow() {
+        mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
+
         if (!DisplayController.isTransientTaskbar(mControllers.taskbarActivityContext)
                 || mControllers.taskbarAllAppsController.isOpen()) {
             mControllers.taskbarOverlayController.hideWindow();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 6f2d459..87e19be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -233,7 +233,7 @@
         mTaskbarNavButtonTranslationY =
                 controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
         mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
-                .getNavButtonTranslationYForInAppDisplay();
+                .getTaskbarNavButtonTranslationYForInAppDisplay();
 
         mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
index fdbbbb0..f389d7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutController.kt
@@ -72,32 +72,63 @@
         lp.marginEnd = horizontalMargin
         container.addView(flyout, lp)
 
+        this.flyout = flyout
+        flyout.showFromCollapsed(message) { showFlyout(AnimationType.COLLAPSE, onEnd) }
+    }
+
+    private fun showFlyout(animationType: AnimationType, endAction: () -> Unit) {
+        val flyout = this.flyout ?: return
         val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
-        animator.addUpdateListener { _ ->
-            flyout.updateExpansionProgress(animator.animatedValue as Float)
+        when (animationType) {
+            AnimationType.FADE ->
+                animator.addUpdateListener { _ -> flyout.alpha = animator.animatedValue as Float }
+            AnimationType.COLLAPSE ->
+                animator.addUpdateListener { _ ->
+                    flyout.updateExpansionProgress(animator.animatedValue as Float)
+                }
         }
         animator.addListener(
-            onStart = {
-                val flyoutTop = flyout.top + flyout.translationY
-                // If the top position of the flyout is negative, then it's bleeding over the
-                // top boundary of its parent view
-                if (flyoutTop < 0) callbacks.extendTopBoundary(space = -flyoutTop.toInt())
-            },
+            onStart = { extendTopBoundary() },
             onEnd = {
-                onEnd()
+                endAction()
                 flyout.setOnClickListener { callbacks.flyoutClicked() }
             },
         )
-        flyout.showFromCollapsed(message) { animator.start() }
-        this.flyout = flyout
+        animator.start()
+    }
+
+    fun updateFlyoutFullyExpanded(message: BubbleBarFlyoutMessage, onEnd: () -> Unit) {
+        val flyout = flyout ?: return
+        hideFlyout(AnimationType.FADE) {
+            flyout.updateData(message) { showFlyout(AnimationType.FADE, onEnd) }
+        }
+    }
+
+    fun updateFlyoutWhileExpanding(message: BubbleBarFlyoutMessage) {
+        val flyout = flyout ?: return
+        flyout.updateData(message) { extendTopBoundary() }
+    }
+
+    private fun extendTopBoundary() {
+        val flyout = flyout ?: return
+        val flyoutTop = flyout.top + flyout.translationY
+        // If the top position of the flyout is negative, then it's bleeding over the
+        // top boundary of its parent view
+        if (flyoutTop < 0) callbacks.extendTopBoundary(space = -flyoutTop.toInt())
     }
 
     fun cancelFlyout(endAction: () -> Unit) {
-        hideFlyout(AnimationType.FADE, endAction)
+        hideFlyout(AnimationType.FADE) {
+            cleanupFlyoutView()
+            endAction()
+        }
     }
 
     fun collapseFlyout(endAction: () -> Unit) {
-        hideFlyout(AnimationType.COLLAPSE, endAction)
+        hideFlyout(AnimationType.COLLAPSE) {
+            cleanupFlyoutView()
+            endAction()
+        }
     }
 
     private fun hideFlyout(animationType: AnimationType, endAction: () -> Unit) {
@@ -112,15 +143,15 @@
                     flyout.updateExpansionProgress(animator.animatedValue as Float)
                 }
         }
-        animator.addListener(
-            onStart = { flyout.setOnClickListener(null) },
-            onEnd = {
-                container.removeView(flyout)
-                this@BubbleBarFlyoutController.flyout = null
-                callbacks.resetTopBoundary()
-                endAction()
-            },
-        )
+        animator.addListener(onStart = { flyout.setOnClickListener(null) }, onEnd = { endAction() })
         animator.start()
     }
+
+    private fun cleanupFlyoutView() {
+        container.removeView(flyout)
+        this@BubbleBarFlyoutController.flyout = null
+        callbacks.resetTopBoundary()
+    }
+
+    fun hasFlyout() = flyout != null
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
index bb8a392..af8aaf8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutView.kt
@@ -198,6 +198,8 @@
             } else {
                 -positioner.distanceToCollapsedPosition.x
             }
+        // TODO: b/277815200 - before collapsing, recalculate translationToCollapsedPosition because
+        // the collapsed position may have changed
         val tyToCollapsedPosition =
             positioner.distanceToCollapsedPosition.y + triangleHeight - triangleOverlap
         translationToCollapsedPosition = PointF(txToCollapsedPosition, tyToCollapsedPosition)
@@ -217,6 +219,12 @@
         scheduler.runAfterLayout(expandAnimation)
     }
 
+    /** Updates the content of the flyout and schedules [afterLayout] to run after a layout pass. */
+    fun updateData(flyoutMessage: BubbleBarFlyoutMessage, afterLayout: () -> Unit) {
+        setData(flyoutMessage)
+        scheduler.runAfterLayout(afterLayout)
+    }
+
     private fun setData(flyoutMessage: BubbleBarFlyoutMessage) {
         if (flyoutMessage.icon != null) {
             icon.visibility = VISIBLE
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 4ad65e1..228dc91 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -419,8 +419,10 @@
             mDepthController.setActivityStarted(isStarted());
         }
 
-        if ((changeBits & ACTIVITY_STATE_RESUMED) != 0 && mTaskbarUIController != null) {
-            mTaskbarUIController.onLauncherPausedOrResumed(isPaused());
+        if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) {
+            if (!FeatureFlags.enableHomeTransitionListener() && mTaskbarUIController != null) {
+                mTaskbarUIController.onLauncherVisibilityChanged(hasBeenResumed());
+            }
         }
 
         super.onActivityFlagsChanged(changeBits);
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 015a449..cff352c 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -309,7 +309,9 @@
      * changes in the WM hierarchy (ie. starting recents transition when you are already over home).
      */
     public boolean useSyntheticRecentsTransition() {
-        return mRunningTask.isHomeTask() && Flags.enableFallbackOverviewInWindow();
+        return mRunningTask.isHomeTask()
+                && (Flags.enableFallbackOverviewInWindow()
+                        || Flags.enableLauncherOverviewInWindow());
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 66112c1..1f6c671 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -155,7 +155,7 @@
             mContainerInterface.onAssistantVisibilityChanged(0.f);
         }
 
-        if (SEPARATE_RECENTS_ACTIVITY.get()) {
+        if (SEPARATE_RECENTS_ACTIVITY.get() || Flags.enableLauncherOverviewInWindow()) {
             mIsDefaultHome = false;
             if (defaultHome == null) {
                 defaultHome = mMyHomeIntent.getComponent();
@@ -179,7 +179,7 @@
         } else {
             // The default home app is a different launcher. Use the fallback Overview instead.
 
-            if (Flags.enableFallbackOverviewInWindow()) {
+            if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
                 mContainerInterface = FallbackWindowInterface.getInstance();
             } else {
                 mContainerInterface = FallbackActivityInterface.INSTANCE;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 7d5bd37..8fc1a78 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -109,7 +109,8 @@
         boolean isOpeningHome = Arrays.stream(appTargets).filter(app -> app.mode == MODE_OPENING
                         && app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME)
                 .count() > 0;
-        if (appCount == 0 && (!Flags.enableFallbackOverviewInWindow() || isOpeningHome)) {
+        if (appCount == 0 && (!(Flags.enableFallbackOverviewInWindow()
+                || Flags.enableLauncherOverviewInWindow()) || isOpeningHome)) {
             ActiveGestureProtoLogProxy.logOnRecentsAnimationStartCancelled();
             // Edge case, if there are no closing app targets, then Launcher has nothing to handle
             notifyAnimationCanceled();
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 56c978a..0b6794c 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -295,7 +295,8 @@
 
         // TODO:(b/365777482) if flag is enabled, but on launcher it will crash.
         if(containerInterface.getCreatedContainer() instanceof RecentsWindowManager
-                && Flags.enableFallbackOverviewInWindow()){
+                && (Flags.enableFallbackOverviewInWindow()
+                        || Flags.enableLauncherOverviewInWindow())) {
             mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent, options,
                     mCallbacks, gestureState.useSyntheticRecentsTransition());
             mRecentsWindowsManager.startRecentsWindow(mCallbacks);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1481ef2..032e755 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -675,7 +675,7 @@
         mDesktopVisibilityController = new DesktopVisibilityController(this);
         mTaskbarManager = new TaskbarManager(
                 this, mAllAppsActionManager, mNavCallbacks, mDesktopVisibilityController);
-        if(Flags.enableFallbackOverviewInWindow()) {
+        if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
             mRecentsWindowManager = new RecentsWindowManager(this);
         }
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
@@ -927,9 +927,6 @@
             BubbleControllers bubbleControllers = tac != null ? tac.getBubbleControllers() : null;
             boolean isOnBubbles = bubbleControllers != null
                     && BubbleBarInputConsumer.isEventOnBubbles(tac, event);
-            if (isInSwipeUpTouchRegion && tac != null) {
-                tac.closeKeyboardQuickSwitchView();
-            }
             if (mDeviceState.isButtonNavMode()
                     && mDeviceState.supportsAssistantGestureInButtonNav()) {
                 reasonString.append("in three button mode which supports Assistant gesture");
@@ -1410,8 +1407,10 @@
     }
 
     public AbsSwipeUpHandler.Factory getSwipeUpHandlerFactory() {
+        boolean recentsInWindow =
+                Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow();
         return mOverviewComponentObserver.isHomeAndOverviewSame()
-                ? mLauncherSwipeHandlerFactory : (Flags.enableFallbackOverviewInWindow()
+                ? mLauncherSwipeHandlerFactory : (recentsInWindow
                 ? mRecentsWindowSwipeHandlerFactory : mFallbackSwipeHandlerFactory);
     }
 
diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
index 3b58dfc..cf6b04e 100644
--- a/quickstep/src/com/android/quickstep/ViewUtils.java
+++ b/quickstep/src/com/android/quickstep/ViewUtils.java
@@ -23,6 +23,7 @@
 
 import com.android.launcher3.Utilities;
 
+import java.util.ArrayList;
 import java.util.function.BooleanSupplier;
 
 /**
@@ -129,4 +130,18 @@
             }
         }
     }
+
+    /**
+     * Adds the view to the list of accessible children.
+     *
+     * @param view The view to add.
+     * @param outChildren The list of accessible children.
+     */
+    public static void addAccessibleChildToList(View view, ArrayList<View> outChildren) {
+        if (view.includeForAccessibility()) {
+            outChildren.add(view);
+        } else {
+            view.addChildrenForAccessibility(outChildren);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 5a4c769..daad6b7 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -78,7 +78,7 @@
     }
 
     private static BaseContainerInterface<RecentsState, ?> getContainerInterface() {
-        return Flags.enableFallbackOverviewInWindow()
+        return (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow())
                 ? FallbackWindowInterface.getInstance()
                 : FallbackActivityInterface.INSTANCE;
     }
@@ -294,7 +294,8 @@
         }
 
         // disabling this so app icons aren't drawn on top of recent tasks.
-        if (isOverlayEnabled && !Flags.enableFallbackOverviewInWindow()) {
+        if (isOverlayEnabled && !(Flags.enableFallbackOverviewInWindow()
+                || Flags.enableLauncherOverviewInWindow())) {
             runActionOnRemoteHandles(remoteTargetHandle ->
                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
         }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e19b338..c4198db 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -157,6 +157,7 @@
         mStartDisplacement = continuingPreviousGesture ? 0 : -mTouchSlop;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
         mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
+
     }
 
     @Override
@@ -426,8 +427,9 @@
             notifyGestureStarted(true /*isLikelyToStartNewTask*/);
         } else {
             // todo differentiate intent based on if we are on home or in app for overview in window
-            Intent intent = new Intent(Flags.enableFallbackOverviewInWindow()
-                ? mInteractionHandler.getHomeIntent()
+            boolean useHomeIntentForWindow = Flags.enableFallbackOverviewInWindow()
+                    || Flags.enableLauncherOverviewInWindow();
+            Intent intent = new Intent(useHomeIntentForWindow ? mInteractionHandler.getHomeIntent()
                 : mInteractionHandler.getLaunchIntent());
             intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
             mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 6b145bd..15b0a6b 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -39,6 +39,7 @@
 import com.android.launcher3.util.rects.set
 import com.android.quickstep.BaseContainerInterface
 import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.ViewUtils
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.util.RecentsOrientedState
 import com.android.systemui.shared.recents.model.Task
@@ -313,6 +314,11 @@
 
     override fun getThumbnailFullscreenParams() = snapshotDrawParams
 
+    override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {
+        super.addChildrenForAccessibility(outChildren)
+        ViewUtils.addAccessibleChildToList(backgroundView, outChildren)
+    }
+
     companion object {
         private const val TAG = "DesktopTaskView"
         private const val DEBUG = false
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index dc5ffee..959516f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -29,6 +29,7 @@
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskUtils
+import com.android.quickstep.ViewUtils.addAccessibleChildToList
 import com.android.quickstep.recents.di.RecentsDependencies
 import com.android.quickstep.recents.di.get
 import com.android.quickstep.recents.di.getScope
@@ -182,12 +183,4 @@
         showWindowsView?.let { addAccessibleChildToList(it, outChildren) }
         digitalWellBeingToast?.let { addAccessibleChildToList(it, outChildren) }
     }
-
-    private fun addAccessibleChildToList(view: View, outChildren: ArrayList<View>) {
-        if (view.includeForAccessibility()) {
-            outChildren.add(view)
-        } else {
-            view.addChildrenForAccessibility(outChildren)
-        }
-    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt
index 0eea741..50bb9bc 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/flyout/BubbleBarFlyoutControllerTest.kt
@@ -115,11 +115,13 @@
     fun hideFlyout_removedFromContainer() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             flyoutController.setUpAndShowFlyout(flyoutMessage) {}
+            assertThat(flyoutController.hasFlyout()).isTrue()
             assertThat(flyoutContainer.childCount).isEqualTo(1)
             flyoutController.collapseFlyout {}
             animatorTestRule.advanceTimeBy(300)
         }
         assertThat(flyoutContainer.childCount).isEqualTo(0)
+        assertThat(flyoutController.hasFlyout()).isFalse()
     }
 
     @Test
@@ -189,6 +191,61 @@
         assertThat(flyoutCallbacks.flyoutClicked).isTrue()
     }
 
+    @Test
+    fun updateFlyoutWhileExpanding() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            flyoutController.setUpAndShowFlyout(flyoutMessage) {}
+            assertThat(flyoutController.hasFlyout()).isTrue()
+            val flyout = flyoutContainer.findViewById<View>(R.id.bubble_bar_flyout_view)
+            assertThat(flyout.findViewById<TextView>(R.id.bubble_flyout_text).text)
+                .isEqualTo("message")
+            // advance the animation about halfway
+            animatorTestRule.advanceTimeBy(100)
+        }
+        assertThat(flyoutController.hasFlyout()).isTrue()
+
+        val newFlyoutMessage = flyoutMessage.copy(message = "new message")
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            val flyout = flyoutContainer.findViewById<View>(R.id.bubble_bar_flyout_view)
+            // set negative translation to verify that the top boundary extends as a result of
+            // updating while expanding
+            flyout.translationY = -50f
+            flyoutController.updateFlyoutWhileExpanding(newFlyoutMessage)
+            assertThat(flyout.findViewById<TextView>(R.id.bubble_flyout_text).text)
+                .isEqualTo("new message")
+        }
+        assertThat(flyoutCallbacks.topBoundaryExtendedSpace).isEqualTo(50)
+    }
+
+    @Test
+    fun updateFlyoutFullyExpanded() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            flyoutController.setUpAndShowFlyout(flyoutMessage) {}
+            animatorTestRule.advanceTimeBy(300)
+        }
+        assertThat(flyoutController.hasFlyout()).isTrue()
+
+        val newFlyoutMessage = flyoutMessage.copy(message = "new message")
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            val flyout = flyoutContainer.findViewById<View>(R.id.bubble_bar_flyout_view)
+            // set negative translation to verify that the top boundary extends as a result of
+            // updating while fully expanded
+            flyout.translationY = -50f
+            flyoutController.updateFlyoutFullyExpanded(newFlyoutMessage) {}
+
+            // advance the timer so that the fade out animation plays
+            animatorTestRule.advanceTimeBy(250)
+            assertThat(flyout.alpha).isEqualTo(0)
+            assertThat(flyout.findViewById<TextView>(R.id.bubble_flyout_text).text)
+                .isEqualTo("new message")
+
+            // advance the timer so that the fade in animation plays
+            animatorTestRule.advanceTimeBy(250)
+            assertThat(flyout.alpha).isEqualTo(1)
+        }
+        assertThat(flyoutCallbacks.topBoundaryExtendedSpace).isEqualTo(50)
+    }
+
     class FakeFlyoutCallbacks : FlyoutCallbacks {
 
         var topBoundaryExtendedSpace = 0
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
index 9bc1c59..2c275f4 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
@@ -22,7 +22,7 @@
 import android.platform.test.annotations.PlatinumTest;
 
 import com.android.launcher3.tapl.Overview;
-import com.android.launcher3.tapl.OverviewTask.OverviewSplitTask;
+import com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer;
 import com.android.launcher3.tapl.OverviewTaskMenu;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -76,7 +76,7 @@
         taskMenu.touchOutsideTaskMenuToDismiss();
 
         OverviewTaskMenu splitMenu = overview.getCurrentTask().tapMenu(
-                        OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT);
+                        OverviewTaskContainer.SPLIT_BOTTOM_OR_RIGHT);
         assertTrue("App info item not appearing in expanded split task's menu.",
                 splitMenu.hasMenuItem("App info"));
         splitMenu.touchOutsideTaskMenuToDismiss();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
index 694a382..2a8afbf 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
@@ -21,6 +21,8 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
 import com.android.launcher3.BuildConfig
+import com.android.launcher3.tapl.LaunchedAppState
+import com.android.launcher3.tapl.OverviewTask
 import com.android.launcher3.ui.AbstractLauncherUiTest
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape
 import com.android.launcher3.uioverrides.QuickstepLauncher
@@ -45,22 +47,16 @@
     @Test
     @PortraitLandscape
     fun enterDesktopViaOverviewMenu() {
-        // Move last launched TEST_ACTIVITY_2 into Desktop
-        mLauncher.workspace
-            .switchToOverview()
-            .getTestActivityTask(TEST_ACTIVITY_2)
-            .tapMenu()
-            .tapDesktopMenuItem()
-        assertTestAppLaunched(TEST_ACTIVITY_2)
+        mLauncher.workspace.switchToOverview()
+        moveTaskToDesktop(TEST_ACTIVITY_2) // Move last launched TEST_ACTIVITY_2 into Desktop
 
         // Scroll back to TEST_ACTIVITY_1, then move it into Desktop
         mLauncher
             .goHome()
             .switchToOverview()
             .apply { flingForward() }
-            .getTestActivityTask(TEST_ACTIVITY_1)
-            .tapMenu()
-            .tapDesktopMenuItem()
+            .also { moveTaskToDesktop(TEST_ACTIVITY_1) }
+
         TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
 
         // Launch static DesktopTaskView
@@ -73,6 +69,83 @@
         TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
     }
 
+    @Test
+    @PortraitLandscape
+    fun dismissFocusedTasks_thenDesktopIsCentered() {
+        // Create DesktopTaskView
+        mLauncher.goHome().switchToOverview()
+        moveTaskToDesktop(TEST_ACTIVITY_2)
+
+        // Create a new task activity to be the focused task
+        mLauncher.goHome()
+        startTestActivity(TEST_ACTIVITY_EXTRA)
+
+        val overview = mLauncher.goHome().switchToOverview()
+
+        // Dismiss focused task
+        val focusedTask1 = overview.currentTask
+        assertTaskContentDescription(focusedTask1, TEST_ACTIVITY_EXTRA)
+        focusedTask1.dismiss()
+
+        // Dismiss new focused task
+        val focusedTask2 = overview.currentTask
+        assertTaskContentDescription(focusedTask2, TEST_ACTIVITY_1)
+        focusedTask2.dismiss()
+
+        // Dismiss DesktopTaskView
+        val desktopTask = overview.currentTask
+        assertWithMessage("The current task is not a Desktop.").that(desktopTask.isDesktop).isTrue()
+        desktopTask.dismiss()
+
+        assertWithMessage("Still have tasks after dismissing all the tasks")
+            .that(mLauncher.workspace.switchToOverview().hasTasks())
+            .isFalse()
+    }
+
+    @Test
+    @PortraitLandscape
+    fun dismissFocusedTask_thenDesktopTask_thenFocusedTaskIsCentered() {
+        // Create extra activity to be DesktopTaskView
+        startTestActivity(TEST_ACTIVITY_EXTRA)
+        mLauncher.goHome().switchToOverview()
+        val desktop = moveTaskToDesktop(TEST_ACTIVITY_EXTRA)
+
+        val overview = desktop.switchToOverview()
+
+        // Dismiss focused task
+        val focusedTask1 = overview.getTestActivityTask(TEST_ACTIVITY_2)
+        assertTaskContentDescription(focusedTask1, TEST_ACTIVITY_2)
+        focusedTask1.dismiss()
+
+        // Dismiss DesktopTaskView
+        val desktopTask = overview.currentTask
+        assertWithMessage("The current task is not a Desktop.").that(desktopTask.isDesktop).isTrue()
+        desktopTask.dismiss()
+
+        // Dismiss focused task
+        val focusedTask2 = overview.currentTask
+        assertTaskContentDescription(focusedTask2, TEST_ACTIVITY_1)
+        focusedTask2.dismiss()
+
+        assertWithMessage("Still have tasks after dismissing all the tasks")
+            .that(mLauncher.workspace.switchToOverview().hasTasks())
+            .isFalse()
+    }
+
+    private fun assertTaskContentDescription(task: OverviewTask, activityIndex: Int) {
+        assertWithMessage("The current task content description is not TestActivity$activityIndex.")
+            .that(task.containsContentDescription("TestActivity$activityIndex"))
+            .isTrue()
+    }
+
+    private fun moveTaskToDesktop(activityIndex: Int): LaunchedAppState {
+        return mLauncher.overview
+            .getTestActivityTask(activityIndex)
+            .tapMenu()
+            .tapDesktopMenuItem()
+            .also { assertTestAppLaunched(activityIndex) }
+    }
+
     private fun startTestAppsWithCheck() {
         TEST_ACTIVITIES.forEach {
             startTestActivity(it)
@@ -91,7 +164,7 @@
             .that(
                 mDevice.wait(
                     Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity$index")),
-                    DEFAULT_UI_TIMEOUT
+                    DEFAULT_UI_TIMEOUT,
                 )
             )
             .isTrue()
@@ -100,6 +173,7 @@
     companion object {
         const val TEST_ACTIVITY_1 = 2
         const val TEST_ACTIVITY_2 = 3
+        const val TEST_ACTIVITY_EXTRA = 4
         val TEST_ACTIVITIES = listOf(TEST_ACTIVITY_1, TEST_ACTIVITY_2)
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt b/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt
index 82361aa..99c74be 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitScreenTestUtils.kt
@@ -43,11 +43,11 @@
         val currentTask = overviewWithSplitPair.currentTask
         currentTask.containsContentDescription(
             By.pkg(AbstractLauncherUiTest.getAppPackageName()).text("TestActivity3").toString(),
-            OverviewTask.OverviewSplitTask.SPLIT_TOP_OR_LEFT
+            OverviewTask.OverviewTaskContainer.SPLIT_TOP_OR_LEFT,
         )
         currentTask.containsContentDescription(
             By.pkg(AbstractLauncherUiTest.getAppPackageName()).text("TestActivity2").toString(),
-            OverviewTask.OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT
+            OverviewTask.OverviewTaskContainer.SPLIT_BOTTOM_OR_RIGHT,
         )
         return overviewWithSplitPair
     }
diff --git a/res/layout/widgets_two_pane_sheet_paged_view.xml b/res/layout/widgets_two_pane_sheet_paged_view.xml
index 71c77b5..33a50b0 100644
--- a/res/layout/widgets_two_pane_sheet_paged_view.xml
+++ b/res/layout/widgets_two_pane_sheet_paged_view.xml
@@ -15,7 +15,7 @@
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto">
 
-    <FrameLayout
+    <LinearLayout
         android:id="@+id/widgets_two_pane_sheet_paged_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -23,40 +23,17 @@
         android:layout_gravity="start"
         android:clipChildren="false"
         android:clipToPadding="false"
-        android:layout_alignParentStart="true">
-        <!-- Note: the paddingHorizontal has to be on WidgetPagedView level so that talkback
-         correctly orders the lists to be after the search and suggestions header. See b/209579563.
-          -->
-        <com.android.launcher3.widget.picker.WidgetPagedView
-            android:id="@+id/widgets_view_pager"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:clipToPadding="false"
-            android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
-            android:descendantFocusability="afterDescendants"
-            launcher:pageIndicator="@+id/tabs" >
-
-            <com.android.launcher3.widget.picker.WidgetsRecyclerView
-                android:id="@+id/primary_widgets_list_view"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:clipToPadding="false" />
-
-            <com.android.launcher3.widget.picker.WidgetsRecyclerView
-                android:id="@+id/work_widgets_list_view"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:clipToPadding="false" />
-
-        </com.android.launcher3.widget.picker.WidgetPagedView>
-
+        android:layout_alignParentStart="true"
+        android:orientation="vertical">
         <!-- SearchAndRecommendationsView without the tab layout as well -->
         <!-- Note: the horizontal padding matches with the WidgetPagedView -->
-        <com.android.launcher3.views.StickyHeaderLayout
+        <LinearLayout
             android:id="@+id/search_and_recommendations_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:clipToOutline="true"
+            android:elevation="1dp"
+            android:background="?attr/widgetPickerPrimarySurfaceColor"
             android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
             android:orientation="vertical">
 
@@ -67,6 +44,7 @@
                 android:orientation="horizontal"
                 android:background="?attr/widgetPickerPrimarySurfaceColor"
                 android:gravity="center_vertical"
+                android:layout_marginBottom="8dp"
                 launcher:layout_sticky="true">
                 <FrameLayout
                     android:layout_width="0dp"
@@ -98,7 +76,6 @@
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:id="@+id/suggestions_header"
-                android:layout_marginTop="8dp"
                 android:orientation="horizontal"
                 android:background="?attr/widgetPickerPrimarySurfaceColor"
                 launcher:layout_sticky="true">
@@ -140,6 +117,31 @@
                     style="?android:attr/borderlessButtonStyle" />
 
             </com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
-        </com.android.launcher3.views.StickyHeaderLayout>
-    </FrameLayout>
+        </LinearLayout>
+        <!-- Note: the paddingHorizontal has to be on WidgetPagedView level so that talkback
+ correctly orders the lists to be after the search and suggestions header. See b/209579563.
+  -->
+        <com.android.launcher3.widget.picker.WidgetPagedView
+            android:id="@+id/widgets_view_pager"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:clipToPadding="false"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
+            android:descendantFocusability="afterDescendants"
+            launcher:pageIndicator="@+id/tabs" >
+
+            <com.android.launcher3.widget.picker.WidgetsRecyclerView
+                android:id="@+id/primary_widgets_list_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:clipToPadding="false" />
+
+            <com.android.launcher3.widget.picker.WidgetsRecyclerView
+                android:id="@+id/work_widgets_list_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:clipToPadding="false" />
+
+        </com.android.launcher3.widget.picker.WidgetPagedView>
+    </LinearLayout>
 </merge>
diff --git a/res/layout/widgets_two_pane_sheet_recyclerview.xml b/res/layout/widgets_two_pane_sheet_recyclerview.xml
index c6b3b74..94f141b 100644
--- a/res/layout/widgets_two_pane_sheet_recyclerview.xml
+++ b/res/layout/widgets_two_pane_sheet_recyclerview.xml
@@ -15,28 +15,22 @@
 <merge xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto">
 
-    <FrameLayout
+    <LinearLayout
         android:id="@+id/widgets_two_pane_sheet_recyclerview"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:gravity="start"
         android:layout_gravity="start"
         android:clipChildren="false"
-        android:layout_alignParentStart="true">
-
-        <com.android.launcher3.widget.picker.WidgetsRecyclerView
-            android:id="@+id/primary_widgets_list_view"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
-            android:clipToPadding="false" />
-
+        android:layout_alignParentStart="true"
+        android:orientation="vertical">
         <!-- SearchAndRecommendationsView without the tab layout as well -->
-        <com.android.launcher3.views.StickyHeaderLayout
+        <LinearLayout
             android:id="@+id/search_and_recommendations_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:clipToOutline="true"
+            android:background="?attr/widgetPickerPrimarySurfaceColor"
             android:orientation="vertical">
 
             <LinearLayout
@@ -83,6 +77,13 @@
                 android:background="?attr/widgetPickerPrimarySurfaceColor"
                 launcher:layout_sticky="true">
             </FrameLayout>
-        </com.android.launcher3.views.StickyHeaderLayout>
-    </FrameLayout>
+        </LinearLayout>
+
+        <com.android.launcher3.widget.picker.WidgetsRecyclerView
+            android:id="@+id/primary_widgets_list_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
+            android:clipToPadding="false" />
+    </LinearLayout>
 </merge>
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 3774ae3..2e75261 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -306,10 +306,6 @@
         removeActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_DEFERRED_RESUMED);
     }
 
-    public boolean isPaused() {
-        return !hasBeenResumed() && (mActivityFlags & ACTIVITY_STATE_DEFERRED_RESUMED) == 0;
-    }
-
     /**
      * Sets the activity to appear as resumed.
      */
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
index a013eaa..85ecd58 100644
--- a/src/com/android/launcher3/LauncherModel.kt
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -25,7 +25,6 @@
 import android.util.Pair
 import androidx.annotation.WorkerThread
 import com.android.launcher3.celllayout.CellPosMapper
-import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.model.AddWorkspaceItemsTask
 import com.android.launcher3.model.AllAppsList
@@ -67,8 +66,8 @@
     private val context: Context,
     private val mApp: LauncherAppState,
     private val iconCache: IconCache,
-    private val appFilter: AppFilter,
-    private val mPmHelper: PackageManagerHelper,
+    appFilter: AppFilter,
+    mPmHelper: PackageManagerHelper,
     isPrimaryInstance: Boolean,
 ) {
 
@@ -304,9 +303,6 @@
                     launcherBinder.bindAllApps()
                     launcherBinder.bindDeepShortcuts()
                     launcherBinder.bindWidgets()
-                    if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                        this.modelDelegate.bindAllModelExtras(callbacksList)
-                    }
                     return true
                 } else {
                     mLoaderTask =
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 1094768..0dd2791 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -296,6 +296,10 @@
             // Add the search box above everything else in this container (if the flag is enabled,
             // it's added to drag layer in onAttach instead).
             addView(mSearchContainer);
+            // The search container is visually at the top of the all apps UI, and should thus be
+            // focused by default. It's added to end of the children list, so it needs to be
+            // explicitly marked as focused by default.
+            mSearchContainer.setFocusedByDefault(true);
         }
         mSearchUiManager = (SearchUiManager) mSearchContainer;
     }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 8fe1b34..9e38824 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -62,17 +62,6 @@
      * and set a default value for the flag. This will be the default value on Debug builds.
      * <p>
      */
-    // TODO(Block 3): Clean up flags
-    public static final BooleanFlag ENABLE_WORKSPACE_LOADING_OPTIMIZATION = getDebugFlag(251502424,
-            "ENABLE_WORKSPACE_LOADING_OPTIMIZATION", DISABLED,
-            "load the current workspace screen visible to the user before the rest rather than "
-                    + "loading all of them at once.");
-
-    public static final BooleanFlag CHANGE_MODEL_DELEGATE_LOADING_ORDER = getDebugFlag(251502424,
-            "CHANGE_MODEL_DELEGATE_LOADING_ORDER", DISABLED,
-            "changes the timing of the loading and binding of delegate items during "
-                    + "data preparation for loading the home screen");
-
     // TODO(Block 6): Clean up flags
     public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(270395140,
             "SECONDARY_DRAG_N_DROP_TO_PIN", DISABLED,
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 259e543..bc51a66 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -36,18 +36,24 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.GridOption;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.shapes.AppShape;
+import com.android.launcher3.shapes.AppShapesProvider;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
 import com.android.systemui.shared.Flags;
 
 import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.ExecutionException;
@@ -55,31 +61,44 @@
 /**
  * Exposes various launcher grid options and allows the caller to change them.
  * APIs:
- *      /list_options: List the various available grip options, has following columns
- *          name: name of the grid
+ *      /shape_options: List of various available shape options, where each has following fields
+ *          shape_key: key of the shape option
+ *          title: translated title of the shape option
+ *          path: path of the shape, assuming drawn on 100x100 view port
+ *          is_default: true if this shape option is currently set to the system
+ *
+ *      /grid_options: List the various available grid options, where each has following fields
+ *          name: key of the grid option
  *          rows: number of rows in the grid
  *          cols: number of columns in the grid
  *          preview_count: number of previews available for this grid option. The preview uri
  *                         looks like /preview/<grid-name>/<preview index starting with 0>
- *          is_default: true if this grid is currently active
+ *          is_default: true if this grid option is currently set to the system
  *
- *     /preview: Opens a file stream for the grid preview
+ *     /get_preview: Open a file stream for the grid preview
  *
- *     /default_grid: Call update to set the current grid, with values
- *          name: name of the grid to apply
+ *     /default_grid: Call update to set the current shape and grid, with values
+ *          shape_key: key of the shape to apply
+ *          name: key of the grid to apply
  */
 public class GridCustomizationsProvider extends ContentProvider {
 
     private static final String TAG = "GridCustomizationsProvider";
 
-    private static final String KEY_NAME = "name";
+    private static final String KEY_SHAPE_KEY = "shape_key";
+    private static final String KEY_TITLE = "title";
+    private static final String KEY_PATH = "path";
+    // is_default means if a certain option is currently set to the system
+    private static final String KEY_IS_DEFAULT = "is_default";
+    // Key of grid option. We do not change the name to grid_key for backward compatibility
+    private static final String KEY_GRID_KEY = "name";
     private static final String KEY_ROWS = "rows";
     private static final String KEY_COLS = "cols";
     private static final String KEY_PREVIEW_COUNT = "preview_count";
-    private static final String KEY_IS_DEFAULT = "is_default";
 
-    private static final String KEY_LIST_OPTIONS = "/list_options";
-    private static final String KEY_DEFAULT_GRID = "/default_grid";
+    private static final String KEY_SHAPE_OPTIONS = "/shape_options";
+    private static final String KEY_GRID_OPTIONS = "/grid_options";
+    private static final String KEY_SHAPE_GRID = "/default_grid";
 
     private static final String METHOD_GET_PREVIEW = "get_preview";
 
@@ -91,9 +110,9 @@
     private static final String KEY_SURFACE_PACKAGE = "surface_package";
     private static final String KEY_CALLBACK = "callback";
     public static final String KEY_HIDE_BOTTOM_ROW = "hide_bottom_row";
-    public static final String KEY_GRID_NAME = "grid_name";
 
     private static final int MESSAGE_ID_UPDATE_PREVIEW = 1337;
+    private static final int MESSAGE_ID_UPDATE_SHAPE = 2586;
     private static final int MESSAGE_ID_UPDATE_GRID = 7414;
     private static final int MESSAGE_ID_UPDATE_COLOR = 856;
 
@@ -109,14 +128,39 @@
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
-        switch (uri.getPath()) {
-            case KEY_LIST_OPTIONS: {
+        Context context = getContext();
+        String path = uri.getPath();
+        if (context == null || path == null) {
+            return null;
+        }
+        switch (path) {
+            case KEY_SHAPE_OPTIONS: {
+                if (Flags.newCustomizationPickerUi()) {
+                    MatrixCursor cursor = new MatrixCursor(new String[]{
+                            KEY_SHAPE_KEY, KEY_TITLE, KEY_PATH, KEY_IS_DEFAULT});
+                    List<AppShape> shapes =  AppShapesProvider.INSTANCE.getShapes();
+                    for (int i = 0; i < shapes.size(); i++) {
+                        AppShape shape = shapes.get(i);
+                        cursor.newRow()
+                                .add(KEY_SHAPE_KEY, shape.getKey())
+                                .add(KEY_TITLE, shape.getTitle())
+                                .add(KEY_PATH, shape.getPath())
+                                // TODO (b/348664593): We should fetch the currently-set shape
+                                //  option from the preferences.
+                                .add(KEY_IS_DEFAULT, i == 0);
+                    }
+                    return cursor;
+                } else  {
+                    return null;
+                }
+            }
+            case KEY_GRID_OPTIONS: {
                 MatrixCursor cursor = new MatrixCursor(new String[]{
-                        KEY_NAME, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT, KEY_IS_DEFAULT});
-                InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
-                for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
+                        KEY_GRID_KEY, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT, KEY_IS_DEFAULT});
+                InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
+                for (GridOption gridOption : idp.parseAllGridOptions(context)) {
                     cursor.newRow()
-                            .add(KEY_NAME, gridOption.name)
+                            .add(KEY_GRID_KEY, gridOption.name)
                             .add(KEY_ROWS, gridOption.numRows)
                             .add(KEY_COLS, gridOption.numColumns)
                             .add(KEY_PREVIEW_COUNT, 1)
@@ -159,14 +203,22 @@
             return 0;
         }
         switch (path) {
-            case KEY_DEFAULT_GRID: {
-                String gridName = values.getAsString(KEY_NAME);
+            case KEY_SHAPE_GRID: {
+                if (Flags.newCustomizationPickerUi()) {
+                    String shapeKey = values.getAsString(KEY_SHAPE_KEY);
+                    Optional<AppShape> optionalShape = AppShapesProvider.INSTANCE.getShapes()
+                            .stream().filter(shape -> shape.getKey().equals(shapeKey)).findFirst();
+                    String pathToSet = optionalShape.map(AppShape::getPath).orElse(null);
+                    // TODO (b/348664593): Apply shapeName to the system. This needs to be a
+                    //  synchronous call.
+                }
+                String gridKey = values.getAsString(KEY_GRID_KEY);
                 InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
                 // Verify that this is a valid grid option
                 GridOption match = null;
                 for (GridOption option : idp.parseAllGridOptions(context)) {
                     String name = option.name;
-                    if (name != null && name.equals(gridName)) {
+                    if (name != null && name.equals(gridKey)) {
                         match = option;
                         break;
                     }
@@ -175,7 +227,7 @@
                     return 0;
                 }
 
-                idp.setCurrentGrid(context, gridName);
+                idp.setCurrentGrid(context, gridKey);
                 if (Flags.newCustomizationPickerUi()) {
                     try {
                         // Wait for device profile to be fully reloaded and applied to the launcher
@@ -217,20 +269,30 @@
     }
 
     @Override
-    public Bundle call(String method, String arg, Bundle extras) {
-        if (getContext().checkPermission("android.permission.BIND_WALLPAPER",
+    public Bundle call(@NonNull String method, String arg, Bundle extras) {
+        Context context = getContext();
+        if (context == null) {
+            return null;
+        }
+
+        if (context.checkPermission("android.permission.BIND_WALLPAPER",
                 Binder.getCallingPid(), Binder.getCallingUid())
                 != PackageManager.PERMISSION_GRANTED) {
             return null;
         }
 
-        if (!METHOD_GET_PREVIEW.equals(method)) {
+        if (METHOD_GET_PREVIEW.equals(method)) {
+            return getPreview(extras);
+        } else {
             return null;
         }
-        return getPreview(extras);
     }
 
     private synchronized Bundle getPreview(Bundle request) {
+        Context context = getContext();
+        if (context == null) {
+            return null;
+        }
         RunnableList lifeCycleTracker = new RunnableList();
         try {
             PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(
@@ -268,7 +330,9 @@
         public final PreviewSurfaceRenderer renderer;
         public boolean destroyed = false;
 
-        PreviewLifecycleObserver(RunnableList lifeCycleTracker, PreviewSurfaceRenderer renderer) {
+        PreviewLifecycleObserver(
+                RunnableList lifeCycleTracker,
+                PreviewSurfaceRenderer renderer) {
             this.lifeCycleTracker = lifeCycleTracker;
             this.renderer = renderer;
             lifeCycleTracker.add(() -> destroyed = true);
@@ -284,10 +348,21 @@
                 case MESSAGE_ID_UPDATE_PREVIEW:
                     renderer.hideBottomRow(message.getData().getBoolean(KEY_HIDE_BOTTOM_ROW));
                     break;
+                case MESSAGE_ID_UPDATE_SHAPE:
+                    if (Flags.newCustomizationPickerUi()) {
+                        String shapeKey = message.getData().getString(KEY_SHAPE_KEY);
+                        Optional<AppShape> optionalShape = AppShapesProvider.INSTANCE.getShapes()
+                                .stream()
+                                .filter(shape -> shape.getKey().equals(shapeKey))
+                                .findFirst();
+                        String pathToSet = optionalShape.map(AppShape::getPath).orElse(null);
+                        // TODO (b/348664593): Update launcher preview with the given shape
+                    }
+                    break;
                 case MESSAGE_ID_UPDATE_GRID:
-                    String gridName = message.getData().getString(KEY_GRID_NAME);
-                    if (!TextUtils.isEmpty(gridName)) {
-                        renderer.updateGrid(gridName);
+                    String gridKey = message.getData().getString(KEY_GRID_KEY);
+                    if (!TextUtils.isEmpty(gridKey)) {
+                        renderer.updateGrid(gridKey);
                     }
                     break;
                 case MESSAGE_ID_UPDATE_COLOR:
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 5faa2b8..b51f855 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.Workspace;
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.BgDataModel.Callbacks;
@@ -59,11 +58,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
@@ -100,13 +97,29 @@
     public void bindWorkspace(boolean incrementBindId, boolean isBindSync) {
         Trace.beginSection("BaseLauncherBinder#bindWorkspace");
         try {
-            if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
-                DisjointWorkspaceBinder workspaceBinder =
-                    initWorkspaceBinder(incrementBindId, mBgDataModel.collectWorkspaceScreens());
-                workspaceBinder.bindCurrentWorkspacePages(isBindSync);
-                workspaceBinder.bindOtherWorkspacePages();
-            } else {
-                bindWorkspaceAllAtOnce(incrementBindId, isBindSync);
+            // Save a copy of all the bg-thread collections
+            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
+            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+            final IntArray orderedScreenIds = new IntArray();
+            ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
+            final int workspaceItemCount;
+            synchronized (mBgDataModel) {
+                workspaceItems.addAll(mBgDataModel.workspaceItems);
+                appWidgets.addAll(mBgDataModel.appWidgets);
+                orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
+                mBgDataModel.extraItems.forEach(extraItems::add);
+                if (incrementBindId) {
+                    mBgDataModel.lastBindId++;
+                    mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
+                }
+                mMyBindingId = mBgDataModel.lastBindId;
+                workspaceItemCount = mBgDataModel.itemsIdMap.size();
+            }
+
+            for (Callbacks cb : mCallbacksList) {
+                new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
+                        workspaceItems, appWidgets, extraItems, orderedScreenIds)
+                        .bind(isBindSync, workspaceItemCount);
             }
         } finally {
             Trace.endSection();
@@ -114,53 +127,6 @@
     }
 
     /**
-     * Initializes the WorkspaceBinder for binding.
-     *
-     * @param incrementBindId this is used to stop previously started binding tasks that are
-     *                        obsolete but still queued.
-     * @param workspacePages this allows the Launcher to add the correct workspace screens.
-     */
-    public DisjointWorkspaceBinder initWorkspaceBinder(boolean incrementBindId,
-            IntArray workspacePages) {
-
-        synchronized (mBgDataModel) {
-            if (incrementBindId) {
-                mBgDataModel.lastBindId++;
-                mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
-            }
-            mMyBindingId = mBgDataModel.lastBindId;
-            return new DisjointWorkspaceBinder(workspacePages);
-        }
-    }
-
-    private void bindWorkspaceAllAtOnce(boolean incrementBindId, boolean isBindSync) {
-        // Save a copy of all the bg-thread collections
-        ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
-        ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
-        final IntArray orderedScreenIds = new IntArray();
-        ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
-        final int workspaceItemCount;
-        synchronized (mBgDataModel) {
-            workspaceItems.addAll(mBgDataModel.workspaceItems);
-            appWidgets.addAll(mBgDataModel.appWidgets);
-            orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
-            mBgDataModel.extraItems.forEach(extraItems::add);
-            if (incrementBindId) {
-                mBgDataModel.lastBindId++;
-                mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
-            }
-            mMyBindingId = mBgDataModel.lastBindId;
-            workspaceItemCount = mBgDataModel.itemsIdMap.size();
-        }
-
-        for (Callbacks cb : mCallbacksList) {
-            new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
-                    workspaceItems, appWidgets, extraItems, orderedScreenIds)
-                    .bind(isBindSync, workspaceItemCount);
-        }
-    }
-
-    /**
      * BindDeepShortcuts is abstract because it is a no-op for the go launcher.
      */
     public void bindDeepShortcuts() {
@@ -347,10 +313,8 @@
                 bindItemsInChunks(currentWorkspaceItems, ITEMS_CHUNK, mUiExecutor);
                 bindItemsInChunks(currentAppWidgets, 1, mUiExecutor);
             }
-            if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                mExtraItems.forEach(item ->
-                        executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
-            }
+            mExtraItems.forEach(item ->
+                    executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
 
             RunnableList pendingTasks = new RunnableList();
             Executor pendingExecutor = pendingTasks::add;
@@ -440,126 +404,4 @@
             });
         }
     }
-
-    private class DisjointWorkspaceBinder {
-        private final IntArray mOrderedScreenIds;
-        private final IntSet mCurrentScreenIds = new IntSet();
-        private final Set<Integer> mBoundItemIds = new HashSet<>();
-
-        protected DisjointWorkspaceBinder(IntArray orderedScreenIds) {
-            mOrderedScreenIds = orderedScreenIds;
-
-            for (Callbacks cb : mCallbacksList) {
-                mCurrentScreenIds.addAll(cb.getPagesToBindSynchronously(orderedScreenIds));
-            }
-            if (mCurrentScreenIds.size() == 0) {
-                mCurrentScreenIds.add(Workspace.FIRST_SCREEN_ID);
-            }
-        }
-
-        /**
-         * Binds the currently loaded items in the Data Model. Also signals to the Callbacks[]
-         * that these items have been bound and their respective screens are ready to be shown.
-         *
-         * If this method is called after all the items on the workspace screen have already been
-         * loaded, it will bind all workspace items immediately, and bindOtherWorkspacePages() will
-         * not bind any items.
-         */
-        protected void bindCurrentWorkspacePages(boolean isBindSync) {
-            // Save a copy of all the bg-thread collections
-            ArrayList<ItemInfo> workspaceItems;
-            ArrayList<LauncherAppWidgetInfo> appWidgets;
-            ArrayList<FixedContainerItems> fciList = new ArrayList<>();
-            final int workspaceItemCount;
-            synchronized (mBgDataModel) {
-                workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
-                appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
-                if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                    mBgDataModel.extraItems.forEach(fciList::add);
-                }
-                workspaceItemCount = mBgDataModel.itemsIdMap.size();
-            }
-
-            workspaceItems.forEach(it -> mBoundItemIds.add(it.id));
-            appWidgets.forEach(it -> mBoundItemIds.add(it.id));
-            if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                fciList.forEach(item ->
-                        executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
-            }
-
-            sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
-
-            // Tell the workspace that we're about to start binding items
-            executeCallbacksTask(c -> {
-                c.clearPendingBinds();
-                c.startBinding();
-            }, mUiExecutor);
-
-            // Bind workspace screens
-            executeCallbacksTask(c -> c.bindScreens(mOrderedScreenIds), mUiExecutor);
-
-            bindWorkspaceItems(workspaceItems);
-            bindAppWidgets(appWidgets);
-            executeCallbacksTask(c -> {
-                MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
-                RunnableList onCompleteSignal = new RunnableList();
-                onCompleteSignal.executeAllAndDestroy();
-                c.onInitialBindComplete(mCurrentScreenIds, new RunnableList(), onCompleteSignal,
-                        workspaceItemCount, isBindSync);
-            }, mUiExecutor);
-        }
-
-        protected void bindOtherWorkspacePages() {
-            // Save a copy of all the bg-thread collections
-            ArrayList<ItemInfo> workspaceItems;
-            ArrayList<LauncherAppWidgetInfo> appWidgets;
-
-            synchronized (mBgDataModel) {
-                workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
-                appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
-            }
-
-            workspaceItems.removeIf(it -> mBoundItemIds.contains(it.id));
-            appWidgets.removeIf(it -> mBoundItemIds.contains(it.id));
-
-            sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
-
-            bindWorkspaceItems(workspaceItems);
-            bindAppWidgets(appWidgets);
-
-            executeCallbacksTask(c -> c.finishBindingItems(mCurrentScreenIds), mUiExecutor);
-            mUiExecutor.execute(() -> {
-                MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                ItemInstallQueue.INSTANCE.get(mApp.getContext())
-                        .resumeModelPush(FLAG_LOADER_RUNNING);
-            });
-
-            StringCache cacheClone = mBgDataModel.stringCache.clone();
-            executeCallbacksTask(c -> c.bindStringCache(cacheClone), mUiExecutor);
-        }
-
-        private void bindWorkspaceItems(final ArrayList<ItemInfo> workspaceItems) {
-            // Bind the workspace items
-            int count = workspaceItems.size();
-            for (int i = 0; i < count; i += ITEMS_CHUNK) {
-                final int start = i;
-                final int chunkSize = (i + ITEMS_CHUNK <= count) ? ITEMS_CHUNK : (count - i);
-                executeCallbacksTask(
-                        c -> c.bindItems(workspaceItems.subList(start, start + chunkSize), false),
-                        mUiExecutor);
-            }
-        }
-
-        private void bindAppWidgets(List<LauncherAppWidgetInfo> appWidgets) {
-            // Bind the widgets, one at a time
-            int count = appWidgets.size();
-            for (int i = 0; i < count; i++) {
-                final ItemInfo widget = appWidgets.get(i);
-                executeCallbacksTask(
-                        c -> c.bindItems(Collections.singletonList(widget), false),
-                        mUiExecutor);
-            }
-        }
-    }
 }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 9531d5b..d8ca095 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -328,10 +328,9 @@
             } else {
                 values.put(LauncherSettings.Favorites.CONTAINER, folderId);
             }
-            newId = helper.generateNewItemId();
-            while (idsInUse.contains(newId)) {
+            do {
                 newId = helper.generateNewItemId();
-            }
+            } while (idsInUse.contains(newId));
             values.put(LauncherSettings.Favorites._ID, newId);
             helper.getWritableDatabase().insert(destTableName, null, values);
         }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.java b/src/com/android/launcher3/model/GridSizeMigrationLogic.java
index 12a14b2..f8c8f77 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.java
@@ -35,6 +35,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
@@ -95,10 +96,17 @@
                             t.getDb(), TABLE_NAME, context);
 
             Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
+
+            // Here we keep all the DB ids we have in the destination DB such that we don't assign
+            // an item that we want to add to the destination DB the same id as an already existing
+            // item.
+            List<Integer> idsInUse = new ArrayList<>();
+
             // Migrate hotseat.
-            migrateHotseat(destDeviceState.getNumHotseat(), srcReader, destReader, target);
+            migrateHotseat(destDeviceState.getNumHotseat(), srcReader, destReader, target,
+                    idsInUse);
             // Migrate workspace.
-            migrateWorkspace(srcReader, destReader, target, targetSize);
+            migrateWorkspace(srcReader, destReader, target, targetSize, idsInUse);
 
             dropTable(t.getDb(), TMP_TABLE);
             t.commit();
@@ -113,17 +121,22 @@
         }
     }
 
-    private void migrateHotseat(int destHotseatSize,
+    /**
+     * Handles hotseat migration.
+     */
+    @VisibleForTesting
+    public void migrateHotseat(int destHotseatSize,
             GridSizeMigrationDBController.DbReader srcReader,
-            GridSizeMigrationDBController.DbReader destReader, DatabaseHelper helper) {
+            GridSizeMigrationDBController.DbReader destReader,
+            DatabaseHelper helper, List<Integer> idsInUse) {
         final List<DbEntry> srcHotseatItems =
                 srcReader.loadHotseatEntries();
         final List<DbEntry> dstHotseatItems =
                 destReader.loadHotseatEntries();
 
-
         final List<DbEntry> hotseatToBeAdded =
                 getItemsToBeAdded(srcHotseatItems, dstHotseatItems);
+
         final IntArray toBeRemoved = new IntArray();
         toBeRemoved.addAll(getItemsToBeRemoved(srcHotseatItems, dstHotseatItems));
 
@@ -144,19 +157,19 @@
             removeEntryFromDb(destReader.mDb, destReader.mTableName, toBeRemoved);
         }
 
-        placeHotseatItems(
-                hotseatToBeAdded, dstHotseatItems, destHotseatSize, helper, srcReader, destReader);
+        placeHotseatItems(hotseatToBeAdded, dstHotseatItems, destHotseatSize, helper, srcReader,
+                destReader, idsInUse);
     }
 
     private void placeHotseatItems(List<DbEntry> hotseatToBeAdded,
             List<DbEntry> dstHotseatItems, int destHotseatSize,
             DatabaseHelper helper, GridSizeMigrationDBController.DbReader srcReader,
-            GridSizeMigrationDBController.DbReader destReader) {
+            GridSizeMigrationDBController.DbReader destReader, List<Integer> idsInUse) {
         if (hotseatToBeAdded.isEmpty()) {
             return;
         }
 
-        List<Integer> idsInUse = dstHotseatItems.stream().map(entry -> entry.id).toList();
+        idsInUse.addAll(dstHotseatItems.stream().map(entry -> entry.id).toList());
 
         Collections.sort(hotseatToBeAdded);
 
@@ -168,11 +181,14 @@
         }
     }
 
-    private void migrateWorkspace(GridSizeMigrationDBController.DbReader srcReader,
+
+    /**
+     * Handles workspace migration.
+     */
+    @VisibleForTesting
+    public void migrateWorkspace(GridSizeMigrationDBController.DbReader srcReader,
             GridSizeMigrationDBController.DbReader destReader, DatabaseHelper helper,
-            Point targetSize) {
-
-
+            Point targetSize, List<Integer> idsInUse) {
         final List<DbEntry> srcWorkspaceItems =
                 srcReader.loadAllWorkspaceEntries();
 
@@ -213,7 +229,7 @@
         }
 
         placeWorkspaceItems(workspaceToBeAdded, dstWorkspaceItems, targetSize.x, targetSize.y,
-                helper, srcReader, destReader);
+                helper, srcReader, destReader, idsInUse);
     }
 
     private void placeWorkspaceItems(
@@ -221,13 +237,12 @@
             List<DbEntry> dstWorkspaceItems,
             int trgX, int trgY, DatabaseHelper helper,
             GridSizeMigrationDBController.DbReader srcReader,
-            GridSizeMigrationDBController.DbReader destReader) {
+            GridSizeMigrationDBController.DbReader destReader, List<Integer> idsInUse) {
         if (workspaceToBeAdded.isEmpty()) {
             return;
         }
 
-        List<Integer> idsInUse = dstWorkspaceItems.stream().map(entry -> entry.id).collect(
-                Collectors.toList());
+        idsInUse.addAll(dstWorkspaceItems.stream().map(entry -> entry.id).toList());
 
         Collections.sort(workspaceToBeAdded);
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index b0108c2..06d8b59 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -287,11 +287,6 @@
             }
             logASplit("loadAllApps");
 
-            if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
-                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-                logASplit("allAppsDelegateItems");
-            }
             verifyNotStopped();
             mLauncherBinder.bindAllApps();
             logASplit("bindAllApps");
@@ -356,12 +351,6 @@
                 prefs.putSync(SHOULD_SHOW_SMARTSPACE.to(true));
             }
 
-            if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
-                logASplit("otherDelegateItems");
-                verifyNotStopped();
-            }
-
             updateHandler.updateIcons(allWidgetsList,
                     CachedObjectCachingLogic.INSTANCE,
                     mApp.getModel()::onWidgetLabelsUpdated);
@@ -413,13 +402,6 @@
         }
         logASplit("loadWorkspace");
 
-        if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-            verifyNotStopped();
-            mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
-                    mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-            mModelDelegate.markActive();
-            logASplit("workspaceDelegateItems");
-        }
         mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN
                 && (!enableSmartspaceRemovalToggle() || LauncherPrefs.getPrefs(
                 mApp.getContext()).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true));
@@ -490,14 +472,12 @@
                 IOUtils.closeSilently(c);
             }
 
-            if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
-                mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
-                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-                mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
-                        mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
-                mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
-                mModelDelegate.markActive();
-            }
+            mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
+                    mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+            mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
+                    mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+            mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
+            mModelDelegate.markActive();
 
             // Break early if we've stopped loading
             if (mStopped) {
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 4f0f162..8c3e860 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -325,7 +325,7 @@
     private boolean isThereExistingDb() {
         if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
             // If we already have a new DB, ignore migration
-            Log.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration");
+            FileLog.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration");
             return true;
         }
         return false;
@@ -336,7 +336,7 @@
         if (GridSizeMigrationDBController.needsToMigrate(mContext, idp)) {
             return true;
         }
-        Log.d(TAG, "migrateGridIfNeeded: no grid migration needed");
+        FileLog.d(TAG, "migrateGridIfNeeded: no grid migration needed");
         return false;
     }
 
@@ -344,7 +344,7 @@
         InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
         String targetDbName = new DeviceGridState(idp).getDbFile();
         if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
-            Log.e(TAG, "migrateGridIfNeeded: target db is same as current: " + targetDbName);
+            FileLog.e(TAG, "migrateGridIfNeeded: target db is same as current: " + targetDbName);
             return true;
         }
         return false;
@@ -428,7 +428,7 @@
         }
         InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
         if (!GridSizeMigrationDBController.needsToMigrate(mContext, idp)) {
-            Log.d(TAG, "migrateGridIfNeeded: no grid migration needed");
+            FileLog.d(TAG, "migrateGridIfNeeded: no grid migration needed");
             return true;
         }
         String targetDbName = new DeviceGridState(idp).getDbFile();
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
deleted file mode 100644
index 3ae643e..0000000
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2016 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.provider;
-
-import static com.android.launcher3.LauncherSettings.Favorites.getColumns;
-import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Icon;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.UserManager;
-import android.text.TextUtils;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.model.LoaderCursor;
-import com.android.launcher3.model.UserManagerState;
-import com.android.launcher3.pm.PinRequestHelper;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.PackageManagerHelper;
-
-/**
- * A set of utility methods for Launcher DB used for DB updates and migration.
- */
-public class LauncherDbUtils {
-    /**
-     * Returns a string which can be used as a where clause for DB query to match the given itemId
-     */
-    public static String itemIdMatch(int itemId) {
-        return "_id=" + itemId;
-    }
-
-    public static IntArray queryIntArray(boolean distinct, SQLiteDatabase db, String tableName,
-            String columnName, String selection, String groupBy, String orderBy) {
-        IntArray out = new IntArray();
-        try (Cursor c = db.query(distinct, tableName, new String[] { columnName }, selection, null,
-                groupBy, null, orderBy, null)) {
-            while (c.moveToNext()) {
-                out.add(c.getInt(0));
-            }
-        }
-        return out;
-    }
-
-    public static boolean tableExists(SQLiteDatabase db, String tableName) {
-        try (Cursor c = db.query(true, "sqlite_master", new String[] {"tbl_name"},
-                "tbl_name = ?", new String[] {tableName},
-                null, null, null, null, null)) {
-            return c.getCount() > 0;
-        }
-    }
-
-    public static void dropTable(SQLiteDatabase db, String tableName) {
-        db.execSQL("DROP TABLE IF EXISTS " + tableName);
-    }
-
-    /** Copy fromTable in fromDb to toTable in toDb. */
-    public static void copyTable(SQLiteDatabase fromDb, String fromTable, SQLiteDatabase toDb,
-            String toTable, Context context) {
-        long userSerial = UserCache.INSTANCE.get(context).getSerialNumberForUser(
-                Process.myUserHandle());
-        dropTable(toDb, toTable);
-        Favorites.addTableToDb(toDb, userSerial, false, toTable);
-        if (fromDb != toDb) {
-            toDb.execSQL("ATTACH DATABASE '" + fromDb.getPath() + "' AS from_db");
-            toDb.execSQL(
-                    "INSERT INTO " + toTable + " SELECT " + getColumns(userSerial)
-                        + " FROM from_db." + fromTable);
-            toDb.execSQL("DETACH DATABASE 'from_db'");
-        } else {
-            toDb.execSQL("INSERT INTO " + toTable + " SELECT " + getColumns(userSerial) + " FROM "
-                    + fromTable);
-        }
-    }
-
-    /**
-     * Migrates the legacy shortcuts to deep shortcuts pinned under Launcher.
-     * Removes any invalid shortcut or any shortcut which requires some permission to launch
-     */
-    public static void migrateLegacyShortcuts(Context context, SQLiteDatabase db) {
-        Cursor c = db.query(
-                Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null);
-        UserManagerState ums = new UserManagerState();
-        PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
-        ums.init(UserCache.INSTANCE.get(context),
-                context.getSystemService(UserManager.class));
-        LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, pmHelper,
-                null);
-        IntSet deletedShortcuts = new IntSet();
-
-        while (lc.moveToNext()) {
-            if (lc.user != Process.myUserHandle()) {
-                deletedShortcuts.add(lc.id);
-                continue;
-            }
-            Intent intent = lc.parseIntent();
-            if (intent == null) {
-                deletedShortcuts.add(lc.id);
-                continue;
-            }
-            if (TextUtils.isEmpty(lc.getTitle())) {
-                deletedShortcuts.add(lc.id);
-                continue;
-            }
-
-            // Make sure the target intent can be launched without any permissions. Otherwise remove
-            // the shortcut
-            ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0);
-            if (ri == null || !TextUtils.isEmpty(ri.activityInfo.permission)) {
-                deletedShortcuts.add(lc.id);
-                continue;
-            }
-            PersistableBundle extras = new PersistableBundle();
-            extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, ri.activityInfo.packageName);
-            ShortcutInfo.Builder infoBuilder = new ShortcutInfo.Builder(
-                    context, "migrated_shortcut-" + lc.id)
-                    .setIntent(intent)
-                    .setExtras(extras)
-                    .setShortLabel(lc.getTitle());
-
-            Bitmap bitmap = null;
-            byte[] iconData = lc.getIconBlob();
-            if (iconData != null) {
-                bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.length);
-            }
-            if (bitmap != null) {
-                infoBuilder.setIcon(Icon.createWithBitmap(bitmap));
-            }
-
-            ShortcutInfo info = infoBuilder.build();
-            try {
-                if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) {
-                    deletedShortcuts.add(lc.id);
-                    continue;
-                }
-            } catch (Exception e) {
-                deletedShortcuts.add(lc.id);
-                continue;
-            }
-            ContentValues update = new ContentValues();
-            update.put(Favorites.ITEM_TYPE, Favorites.ITEM_TYPE_DEEP_SHORTCUT);
-            update.put(Favorites.INTENT,
-                    ShortcutKey.makeIntent(info.getId(), context.getPackageName()).toUri(0));
-            db.update(Favorites.TABLE_NAME, update, "_id = ?",
-                    new String[] {Integer.toString(lc.id)});
-        }
-        lc.close();
-        if (!deletedShortcuts.isEmpty()) {
-            db.delete(Favorites.TABLE_NAME,
-                    Utilities.createDbSelectionQuery(Favorites._ID, deletedShortcuts.getArray()),
-                    null);
-        }
-
-        // Drop the unused columns
-        db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconPackage;");
-        db.execSQL("ALTER TABLE " + Favorites.TABLE_NAME + " DROP COLUMN iconResource;");
-    }
-
-    /**
-     * Utility class to simplify managing sqlite transactions
-     */
-    public static class SQLiteTransaction implements AutoCloseable {
-        private final SQLiteDatabase mDb;
-
-        public SQLiteTransaction(SQLiteDatabase db) {
-            mDb = db;
-            db.beginTransaction();
-        }
-
-        public void commit() {
-            mDb.setTransactionSuccessful();
-        }
-
-        @Override
-        public void close() {
-            mDb.endTransaction();
-        }
-
-        public SQLiteDatabase getDb() {
-            return mDb;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.kt b/src/com/android/launcher3/provider/LauncherDbUtils.kt
new file mode 100644
index 0000000..3c68e46
--- /dev/null
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2016 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.provider
+
+import android.content.ContentValues
+import android.content.Context
+import android.content.pm.ShortcutInfo
+import android.database.sqlite.SQLiteDatabase
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.drawable.Icon
+import android.os.PersistableBundle
+import android.os.Process
+import android.os.UserManager
+import android.text.TextUtils
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.Utilities
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.LoaderCursor
+import com.android.launcher3.model.UserManagerState
+import com.android.launcher3.pm.PinRequestHelper
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.IntSet
+import com.android.launcher3.util.PackageManagerHelper
+
+/** A set of utility methods for Launcher DB used for DB updates and migration. */
+object LauncherDbUtils {
+    /**
+     * Returns a string which can be used as a where clause for DB query to match the given itemId
+     */
+    @JvmStatic fun itemIdMatch(itemId: Int): String = "_id=$itemId"
+
+    @JvmStatic
+    fun queryIntArray(
+        distinct: Boolean,
+        db: SQLiteDatabase,
+        tableName: String,
+        columnName: String,
+        selection: String?,
+        groupBy: String?,
+        orderBy: String?,
+    ): IntArray {
+        val out = IntArray()
+        db.query(
+                distinct,
+                tableName,
+                arrayOf(columnName),
+                selection,
+                null,
+                groupBy,
+                null,
+                orderBy,
+                null,
+            )
+            .use { c ->
+                while (c.moveToNext()) {
+                    out.add(c.getInt(0))
+                }
+            }
+        return out
+    }
+
+    @JvmStatic
+    fun tableExists(db: SQLiteDatabase, tableName: String): Boolean =
+        db.query(
+                /* distinct = */ true,
+                /* table = */ "sqlite_master",
+                /* columns = */ arrayOf("tbl_name"),
+                /* selection = */ "tbl_name = ?",
+                /* selectionArgs = */ arrayOf(tableName),
+                /* groupBy = */ null,
+                /* having = */ null,
+                /* orderBy = */ null,
+                /* limit = */ null,
+                /* cancellationSignal = */ null,
+            )
+            .use { c ->
+                return c.count > 0
+            }
+
+    @JvmStatic
+    fun dropTable(db: SQLiteDatabase, tableName: String) =
+        db.execSQL("DROP TABLE IF EXISTS $tableName")
+
+    /** Copy fromTable in fromDb to toTable in toDb. */
+    @JvmStatic
+    fun copyTable(
+        fromDb: SQLiteDatabase,
+        fromTable: String,
+        toDb: SQLiteDatabase,
+        toTable: String,
+        context: Context,
+    ) {
+        val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
+        dropTable(toDb, toTable)
+        LauncherSettings.Favorites.addTableToDb(toDb, userSerial, false, toTable)
+        if (fromDb != toDb) {
+            toDb.run {
+                execSQL("ATTACH DATABASE '${fromDb.path}' AS from_db")
+                execSQL(
+                    "INSERT INTO $toTable SELECT ${LauncherSettings.Favorites.getColumns(userSerial)} FROM from_db.$fromTable"
+                )
+                execSQL("DETACH DATABASE 'from_db'")
+            }
+        } else {
+            toDb.run {
+                execSQL(
+                    "INSERT INTO $toTable SELECT ${
+                        LauncherSettings.Favorites.getColumns(
+                            userSerial
+                        )
+                    } FROM $fromTable"
+                )
+            }
+        }
+    }
+
+    /**
+     * Migrates the legacy shortcuts to deep shortcuts pinned under Launcher. Removes any invalid
+     * shortcut or any shortcut which requires some permission to launch
+     */
+    @JvmStatic
+    fun migrateLegacyShortcuts(context: Context, db: SQLiteDatabase) {
+        val c =
+            db.query(
+                LauncherSettings.Favorites.TABLE_NAME,
+                null,
+                "itemType = 1",
+                null,
+                null,
+                null,
+                null,
+            )
+        val pmHelper = PackageManagerHelper.INSTANCE[context]
+        val ums = UserManagerState()
+        ums.run {
+            init(UserCache.INSTANCE[context], context.getSystemService(UserManager::class.java))
+        }
+        val lc = LoaderCursor(c, LauncherAppState.getInstance(context), ums, pmHelper, null)
+        val deletedShortcuts = IntSet()
+
+        while (lc.moveToNext()) {
+            if (lc.user !== Process.myUserHandle()) {
+                deletedShortcuts.add(lc.id)
+                continue
+            }
+            val intent = lc.parseIntent()
+            if (intent == null) {
+                deletedShortcuts.add(lc.id)
+                continue
+            }
+            if (TextUtils.isEmpty(lc.title)) {
+                deletedShortcuts.add(lc.id)
+                continue
+            }
+
+            // Make sure the target intent can be launched without any permissions. Otherwise remove
+            // the shortcut
+            val ri = context.packageManager.resolveActivity(intent, 0)
+            if (ri == null || !TextUtils.isEmpty(ri.activityInfo.permission)) {
+                deletedShortcuts.add(lc.id)
+                continue
+            }
+            val extras =
+                PersistableBundle().apply {
+                    putString(
+                        IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE,
+                        ri.activityInfo.packageName,
+                    )
+                }
+            val infoBuilder =
+                ShortcutInfo.Builder(context, "migrated_shortcut-${lc.id}")
+                    .setIntent(intent)
+                    .setExtras(extras)
+                    .setShortLabel(lc.title)
+
+            var bitmap: Bitmap? = null
+            val iconData = lc.iconBlob
+            if (iconData != null) {
+                bitmap = BitmapFactory.decodeByteArray(iconData, 0, iconData.size)
+            }
+            if (bitmap != null) {
+                infoBuilder.setIcon(Icon.createWithBitmap(bitmap))
+            }
+
+            val info = infoBuilder.build()
+            try {
+                if (!PinRequestHelper.createRequestForShortcut(context, info).accept()) {
+                    deletedShortcuts.add(lc.id)
+                    continue
+                }
+            } catch (e: Exception) {
+                deletedShortcuts.add(lc.id)
+                continue
+            }
+            val update =
+                ContentValues().apply {
+                    put(
+                        LauncherSettings.Favorites.ITEM_TYPE,
+                        LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT,
+                    )
+                    put(
+                        LauncherSettings.Favorites.INTENT,
+                        ShortcutKey.makeIntent(info.id, context.packageName).toUri(0),
+                    )
+                }
+            db.update(
+                LauncherSettings.Favorites.TABLE_NAME,
+                update,
+                "_id = ?",
+                arrayOf(lc.id.toString()),
+            )
+        }
+        lc.close()
+        if (deletedShortcuts.isEmpty.not()) {
+            db.delete(
+                /* table = */ LauncherSettings.Favorites.TABLE_NAME,
+                /* whereClause = */ Utilities.createDbSelectionQuery(
+                    LauncherSettings.Favorites._ID,
+                    deletedShortcuts.array,
+                ),
+                /* whereArgs = */ null,
+            )
+        }
+
+        // Drop the unused columns
+        db.run {
+            execSQL("ALTER TABLE ${LauncherSettings.Favorites.TABLE_NAME} DROP COLUMN iconPackage;")
+            execSQL(
+                "ALTER TABLE ${LauncherSettings.Favorites.TABLE_NAME} DROP COLUMN iconResource;"
+            )
+        }
+    }
+
+    /** Utility class to simplify managing sqlite transactions */
+    class SQLiteTransaction(val db: SQLiteDatabase) : AutoCloseable {
+        init {
+            db.beginTransaction()
+        }
+
+        fun commit() = db.setTransactionSuccessful()
+
+        override fun close() = db.endTransaction()
+    }
+}
diff --git a/src/com/android/launcher3/shapes/AppShape.kt b/src/com/android/launcher3/shapes/AppShape.kt
new file mode 100644
index 0000000..68200a0
--- /dev/null
+++ b/src/com/android/launcher3/shapes/AppShape.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 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.shapes
+
+class AppShape(val key: String, val title: String, val path: String)
diff --git a/src/com/android/launcher3/shapes/AppShapesProvider.kt b/src/com/android/launcher3/shapes/AppShapesProvider.kt
new file mode 100644
index 0000000..41bac6a
--- /dev/null
+++ b/src/com/android/launcher3/shapes/AppShapesProvider.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.shapes
+
+object AppShapesProvider {
+
+    val shapes =
+        listOf(
+            AppShape(
+                "arch",
+                "arch",
+                "M100 83.46C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116.884 93.916.1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0 77.614 0 100 22.386 100 50V83.46Z",
+            ),
+            AppShape(
+                "4_sided_cookie",
+                "4 sided cookie",
+                "M63.605 3C84.733-6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176-6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C-6.176 15.268 15.267-6.176 36.395 3L39.888 4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3Z",
+            ),
+            AppShape(
+                "seven_sided_cookie",
+                "7 sided cookie",
+                "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82-2.742 55.18-2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24.273 66.266-2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
+            ),
+            AppShape(
+                "sunny",
+                "sunny",
+                "M42.846 4.873C46.084-.531 53.916-.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C-.531 53.916-.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
+            ),
+            AppShape(
+                "circle",
+                "circle",
+                "M99.18 50C99.18 77.162 77.162 99.18 50 99.18 22.838 99.18.82 77.162.82 50 .82 22.839 22.838.82 50 .82 77.162.82 99.18 22.839 99.18 50Z",
+            ),
+            AppShape(
+                "square",
+                "square",
+                "M99.18 53.689C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18H46.311C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758.82 74.306.82 67.434.82 53.689L.82 46.311C.82 32.566.82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694.82 32.566.82 46.311.82L53.689.82C67.434.82 74.306.82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311V53.689Z\n",
+            ),
+        )
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index c8ad564..1860977 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -145,7 +145,9 @@
     protected DeviceProfile mDeviceProfile;
 
     protected TextView mNoWidgetsView;
-    protected StickyHeaderLayout mSearchScrollView;
+    protected LinearLayout mSearchScrollView;
+    // Reference to the mSearchScrollView when it is is a sticky header.
+    private @Nullable StickyHeaderLayout mStickyHeaderLayout;
     protected WidgetRecommendationsView mWidgetRecommendationsView;
     protected LinearLayout mWidgetRecommendationsContainer;
     protected View mTabBar;
@@ -220,7 +222,11 @@
 
     protected void setupViews() {
         mSearchScrollView = findViewById(R.id.search_and_recommendations_container);
-        mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view));
+        if (mSearchScrollView instanceof StickyHeaderLayout) {
+            mStickyHeaderLayout = (StickyHeaderLayout) mSearchScrollView;
+            mStickyHeaderLayout.setCurrentRecyclerView(
+                    findViewById(R.id.primary_widgets_list_view));
+        }
         mNoWidgetsView = findViewById(R.id.no_widgets_text);
         mFastScroller = findViewById(R.id.fast_scroller);
         mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
@@ -284,7 +290,9 @@
             reset();
             resetExpandedHeaders();
             mCurrentWidgetsRecyclerView = recyclerView;
-            mSearchScrollView.setCurrentRecyclerView(recyclerView);
+            if (mStickyHeaderLayout != null) {
+                mStickyHeaderLayout.setCurrentRecyclerView(recyclerView);
+            }
         }
     }
 
@@ -313,7 +321,9 @@
             mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop();
         }
         mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
-        mSearchScrollView.reset(/* animate= */ true);
+        if (mStickyHeaderLayout != null) {
+            mStickyHeaderLayout.reset(/* animate= */ true);
+        }
     }
 
     @VisibleForTesting
@@ -1051,7 +1061,7 @@
         }
 
         private int getEmptySpaceHeight() {
-            return mSearchScrollView.getHeaderHeight();
+            return mStickyHeaderLayout != null ? mStickyHeaderLayout.getHeaderHeight() : 0;
         }
 
         void setup(WidgetsRecyclerView recyclerView) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index 8dd1de4..3d3a669 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -396,15 +396,6 @@
         LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
         if (layoutManager == null) return;
 
-        if (position == mVisibleEntries.size() - 2
-                && mVisibleEntries.get(mVisibleEntries.size() - 1)
-                instanceof WidgetsListContentEntry) {
-            // If the selected header is in the last position and its content is showing, then
-            // scroll to the final position so the last list of widgets will show.
-            layoutManager.scrollToPosition(mVisibleEntries.size() - 1);
-            return;
-        }
-
         // Scroll to the header view's current offset, accounting for the recycler view's padding.
         // If the header view couldn't be found, then it will appear at the top of the list.
         layoutManager.scrollToPositionWithOffset(
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationDBControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
similarity index 94%
rename from tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationDBControllerTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
index c6f291d..7933331 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationDBControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
@@ -41,10 +41,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-/** Unit tests for [GridSizeMigrationUtil] */
+/** Unit tests for [GridSizeMigrationDBController, GridSizeMigrationLogic] */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class GridSizeMigrationUtilTest {
+class GridSizeMigrationTest {
 
     private lateinit var modelHelper: LauncherModelHelper
     private lateinit var context: Context
@@ -130,13 +130,21 @@
         val srcReader = DbReader(db, TMP_TABLE, context)
         val destReader = DbReader(db, TABLE_NAME, context)
         if (Flags.gridMigrationRefactor()) {
-            val gridSizeMigrationLogic = GridSizeMigrationLogic()
-            gridSizeMigrationLogic.migrateGrid(
-                context,
-                DeviceGridState(context),
-                DeviceGridState(idp),
+            var gridSizeMigrationLogic = GridSizeMigrationLogic()
+            val idsInUse = mutableListOf<Int>()
+            gridSizeMigrationLogic.migrateHotseat(
+                idp.numDatabaseHotseatIcons,
+                srcReader,
+                destReader,
                 dbHelper,
-                db,
+                idsInUse,
+            )
+            gridSizeMigrationLogic.migrateWorkspace(
+                srcReader,
+                destReader,
+                dbHelper,
+                Point(idp.numColumns, idp.numRows),
+                idsInUse,
             )
         } else {
             GridSizeMigrationDBController.migrate(
@@ -266,12 +274,20 @@
         // migrate from A -> B
         if (Flags.gridMigrationRefactor()) {
             var gridSizeMigrationLogic = GridSizeMigrationLogic()
-            gridSizeMigrationLogic.migrateGrid(
-                context,
-                DeviceGridState(context),
-                DeviceGridState(idp),
+            val idsInUse = mutableListOf<Int>()
+            gridSizeMigrationLogic.migrateHotseat(
+                idp.numDatabaseHotseatIcons,
+                readerGridA,
+                readerGridB,
                 dbHelper,
-                db,
+                idsInUse,
+            )
+            gridSizeMigrationLogic.migrateWorkspace(
+                readerGridA,
+                readerGridB,
+                dbHelper,
+                Point(idp.numColumns, idp.numRows),
+                idsInUse,
             )
         } else {
             GridSizeMigrationDBController.migrate(
@@ -445,12 +461,20 @@
     ) {
         if (Flags.gridMigrationRefactor()) {
             var gridSizeMigrationLogic = GridSizeMigrationLogic()
-            gridSizeMigrationLogic.migrateGrid(
-                context,
-                DeviceGridState(context),
-                DeviceGridState(idp),
+            val idsInUse = mutableListOf<Int>()
+            gridSizeMigrationLogic.migrateHotseat(
+                idp.numDatabaseHotseatIcons,
+                srcReader,
+                destReader,
                 dbHelper,
-                db,
+                idsInUse,
+            )
+            gridSizeMigrationLogic.migrateWorkspace(
+                srcReader,
+                destReader,
+                dbHelper,
+                Point(idp.numColumns, idp.numRows),
+                idsInUse,
             )
         } else {
             GridSizeMigrationDBController.migrate(
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index b17cd4d..ef7242f 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -3,6 +3,8 @@
 import android.appwidget.AppWidgetManager
 import android.content.Intent
 import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -203,7 +205,8 @@
         }
 
     @Test
-    fun `When launcher_broadcast_installed_apps and is restore then send installed item broadcast`() {
+    @DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
+    fun `When secure setting true and is restore then send installed item broadcast`() {
         // Given
         val spyContext = spy(context)
         `when`(app.context).thenReturn(spyContext)
@@ -271,6 +274,76 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
+    fun `When broadcast flag true and is restore then send installed item broadcast`() {
+        // Given
+        val spyContext = spy(context)
+        `when`(app.context).thenReturn(spyContext)
+        whenever(
+                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                    any(),
+                    any(),
+                    any(),
+                    any(),
+                )
+            )
+            .thenReturn(listOf(expectedBroadcastModel))
+
+        whenever(
+                FirstScreenBroadcastHelper.sendBroadcastsForModels(
+                    spyContext,
+                    listOf(expectedBroadcastModel),
+                )
+            )
+            .thenCallRealMethod()
+
+        Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0)
+        RestoreDbTask.setPending(spyContext)
+
+        // When
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+            .runSyncOnBackgroundThread()
+
+        // Then
+        val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(spyContext).sendBroadcast(argumentCaptor.capture())
+        val actualBroadcastIntent = argumentCaptor.value
+        assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`)
+        assertEquals(
+            ArrayList(expectedBroadcastModel.installedWorkspaceItems),
+            actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems"),
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.installedHotseatItems),
+            actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems"),
+        )
+        assertEquals(
+            ArrayList(
+                expectedBroadcastModel.firstScreenInstalledWidgets +
+                    expectedBroadcastModel.secondaryScreenInstalledWidgets
+            ),
+            actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems"),
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.pendingCollectionItems),
+            actualBroadcastIntent.getStringArrayListExtra("folderItem"),
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.pendingWorkspaceItems),
+            actualBroadcastIntent.getStringArrayListExtra("workspaceItem"),
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.pendingHotseatItems),
+            actualBroadcastIntent.getStringArrayListExtra("hotseatItem"),
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.pendingWidgetItems),
+            actualBroadcastIntent.getStringArrayListExtra("widgetItem"),
+        )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
     fun `When not a restore then installed item broadcast not sent`() {
         // Given
         val spyContext = spy(context)
@@ -304,7 +377,8 @@
     }
 
     @Test
-    fun `When launcher_broadcast_installed_apps false then installed item broadcast not sent`() {
+    @DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
+    fun `When broadcast flag and secure setting false then installed item broadcast not sent`() {
         // Given
         val spyContext = spy(context)
         `when`(app.context).thenReturn(spyContext)
diff --git a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt b/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
index c08237c..b96dbcd 100644
--- a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
+++ b/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
@@ -136,12 +136,20 @@
         LauncherDbUtils.SQLiteTransaction(dbHelper.writableDatabase).use {
             if (Flags.gridMigrationRefactor()) {
                 val gridSizeMigrationLogic = GridSizeMigrationLogic()
-                gridSizeMigrationLogic.migrateGrid(
-                    context,
-                    srcGrid.toGridState(),
-                    dstGrid.toGridState(),
+                val idsInUse = mutableListOf<Int>()
+                gridSizeMigrationLogic.migrateHotseat(
+                    dstGrid.size.x,
+                    GridSizeMigrationDBController.DbReader(it.db, srcGrid.tableName, context),
+                    GridSizeMigrationDBController.DbReader(it.db, dstGrid.tableName, context),
                     dbHelper,
-                    it.db,
+                    idsInUse,
+                )
+                gridSizeMigrationLogic.migrateWorkspace(
+                    GridSizeMigrationDBController.DbReader(it.db, srcGrid.tableName, context),
+                    GridSizeMigrationDBController.DbReader(it.db, dstGrid.tableName, context),
+                    dbHelper,
+                    dstGrid.size,
+                    idsInUse,
                 )
             } else {
                 GridSizeMigrationDBController.migrate(
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 0edcfea..1002ca4 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -369,7 +369,6 @@
         }
     }
 
-
     int getTaskCount() {
         return getTasks().size();
     }
@@ -441,7 +440,7 @@
                     "Not expecting an actions bar: device is tablet and task is not centered");
             return false;
         }
-        if (task.isTaskSplit() && (!mLauncher.isAppPairsEnabled() || !isTablet)) {
+        if (task.isGrouped() && (!mLauncher.isAppPairsEnabled() || !isTablet)) {
             testLogD(TAG, "Not expecting an actions bar: device is phone and task is split");
             // Overview actions aren't visible for split screen tasks, except for save app pair
             // button on tablets.
@@ -504,11 +503,11 @@
                 "want to assert overview actions view visibility="
                         + isActionsViewVisible()
                         + ", focused task is "
-                        + (task == null ? "null" : (task.isTaskSplit() ? "split" : "not split"))
+                        + (task == null ? "null" : (task.isGrouped() ? "split" : "not split"))
                 )) {
 
             if (isActionsViewVisible()) {
-                if (task.isTaskSplit()) {
+                if (task.isGrouped()) {
                     mLauncher.waitForOverviewObject("action_save_app_pair");
                 } else {
                     mLauncher.waitForOverviewObject("action_buttons");
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 9a8d952..5fd4dac 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -16,9 +16,10 @@
 
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.DEFAULT;
-import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.tapl.OverviewTask.OverviewSplitTask.SPLIT_TOP_OR_LEFT;
+import static com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer.DEFAULT;
+import static com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer.DESKTOP;
+import static com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer.SPLIT_BOTTOM_OR_RIGHT;
+import static com.android.launcher3.tapl.OverviewTask.OverviewTaskContainer.SPLIT_TOP_OR_LEFT;
 
 import android.graphics.Rect;
 
@@ -69,11 +70,11 @@
      * divider between.
      */
     int getVisibleHeight() {
-        if (isTaskSplit()) {
+        if (isGrouped()) {
             return getCombinedSplitTaskHeight();
         }
 
-        UiObject2 taskSnapshot1 = findObjectInTask(DEFAULT.snapshotRes);
+        UiObject2 taskSnapshot1 = findObjectInTask((isDesktop() ? DESKTOP : DEFAULT).snapshotRes);
         return taskSnapshot1.getVisibleBounds().height();
     }
 
@@ -102,7 +103,7 @@
      * divider between.
      */
     int getVisibleWidth() {
-        if (isTaskSplit()) {
+        if (isGrouped()) {
             return getCombinedSplitTaskWidth();
         }
 
@@ -164,8 +165,11 @@
 
             dismissBySwipingUp();
 
+            long numNonDesktopTasks = mOverview.getCurrentTasksForTablet()
+                    .stream().filter(t -> !t.isDesktop()).count();
+
             try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("dismissed")) {
-                if (taskWasFocused) {
+                if (taskWasFocused && numNonDesktopTasks > 0) {
                     mLauncher.assertNotNull("No task became focused",
                             mOverview.getFocusedTaskForTablet());
                 }
@@ -256,7 +260,7 @@
 
     /** Taps the task menu of the split task. Returns the split task's menu object. */
     @NonNull
-    public OverviewTaskMenu tapMenu(OverviewSplitTask task) {
+    public OverviewTaskMenu tapMenu(OverviewTaskContainer task) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "want to tap the task menu")) {
@@ -270,10 +274,6 @@
         }
     }
 
-    boolean isTaskSplit() {
-        return findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes) != null;
-    }
-
     private UiObject2 findObjectInTask(String resName) {
         return mTask.findObject(mLauncher.getOverviewObjectSelector(resName));
     }
@@ -285,8 +285,8 @@
      * TODO(b/342627272): remove Nullable support once the bug causing it to be null is fixed.
      */
     public boolean containsContentDescription(@Nullable String expected,
-            OverviewSplitTask overviewSplitTask) {
-        String actual = findObjectInTask(overviewSplitTask.snapshotRes).getContentDescription();
+            OverviewTaskContainer overviewTaskContainer) {
+        String actual = findObjectInTask(overviewTaskContainer.snapshotRes).getContentDescription();
         if (actual == null && expected == null) {
             return true;
         }
@@ -315,21 +315,31 @@
         }
     }
 
+    boolean isGrouped() {
+        return mType == TaskViewType.GROUPED;
+    }
+
+    public boolean isDesktop() {
+        return mType == TaskViewType.DESKTOP;
+    }
+
     /**
-     * Enum used to specify  which task is retrieved when it is a split task.
+     * Enum used to specify which resource name should be used depending on the type of the task.
      */
-    public enum OverviewSplitTask {
+    public enum OverviewTaskContainer {
         // The main task when the task is not split.
         DEFAULT("snapshot", "icon"),
         // The first task in split task.
         SPLIT_TOP_OR_LEFT("snapshot", "icon"),
         // The second task in split task.
-        SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon");
+        SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon"),
+        // The desktop task.
+        DESKTOP("background", "icon");
 
         public final String snapshotRes;
         public final String iconAppRes;
 
-        OverviewSplitTask(String snapshotRes, String iconAppRes) {
+        OverviewTaskContainer(String snapshotRes, String iconAppRes) {
             this.snapshotRes = snapshotRes;
             this.iconAppRes = iconAppRes;
         }