Merge "Set desktop icon to always use black for the icon" into tm-qpr-dev
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index c638ba9..b1064f7 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -128,10 +128,17 @@
 
   // Bit encoded value to capture pinned and predicted taskbar positions.
   optional int32 cardinality = 2;
+
+  // Container where taskbar was invoked.
+  oneof ParentContainer {
+    TaskSwitcherContainer task_switcher_container = 3;
+  }
 }
 
 // Next value 44
 enum Attribute {
+  option allow_alias = true;
+
   UNKNOWN = 0;
   DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
   BACKUP_RESTORE = 2;       // icon layout restored from backup
@@ -166,7 +173,8 @@
   ALL_APPS_SEARCH_RESULT_SLICE = 19;
   ALL_APPS_SEARCH_RESULT_WIDGETS = 20;
   ALL_APPS_SEARCH_RESULT_PLAY = 21;
-  ALL_APPS_SEARCH_RESULT_SUGGEST = 22;
+  ALL_APPS_SEARCH_RESULT_FALLBACK = 22;
+  ALL_APPS_SEARCH_RESULT_SUGGEST = 22 [deprecated = true];
   ALL_APPS_SEARCH_RESULT_ASSISTANT = 23;
   ALL_APPS_SEARCH_RESULT_CHROMETAB = 24;
   ALL_APPS_SEARCH_RESULT_NAVVYSITE = 25;
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index f08cabe..7ea92b5 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -38,6 +38,7 @@
             app:lottie_loop="true" />
 
         <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/text_content_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_marginStart="@dimen/allset_page_margin_horizontal"
diff --git a/quickstep/res/values-night/colors.xml b/quickstep/res/values-night/colors.xml
index af6e064..6474c48 100644
--- a/quickstep/res/values-night/colors.xml
+++ b/quickstep/res/values-night/colors.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
 
     <color name="gesture_tutorial_back_arrow_color">#99000000</color>
 
@@ -24,4 +24,6 @@
 
     <color name="all_set_page_background">#FF000000</color>
 
+    <!-- Turn on work apps button -->
+    <color name="work_turn_on_stroke">?androidprv:attr/colorAccentSecondaryVariant</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 55df38f..7ac3979 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
 
     <color name="chip_hint_foreground_color">#fff</color>
     <color name="chip_scrim_start_color">#39000000</color>
@@ -95,4 +95,6 @@
     <color name="lottie_yellow400">#fcc934</color>
     <color name="lottie_yellow600">#f9ab00</color>
 
+    <!-- Turn on work apps button -->
+    <color name="work_turn_on_stroke">?androidprv:attr/colorAccentPrimaryVariant</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index ac2c44b..95fea3e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -25,7 +25,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
-import android.annotation.ColorInt;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.TaskTransitionSpec;
@@ -223,17 +222,10 @@
                 WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec();
             } else {
                 // Adjust task transition spec to account for taskbar being visible
-                @ColorInt int taskAnimationBackgroundColor =
-                        DisplayController.isTransientTaskbar(mLauncher)
-                                ? mLauncher.getColor(R.color.transient_taskbar_background)
-                                : mLauncher.getColor(R.color.taskbar_background);
-
-                TaskTransitionSpec customTaskAnimationSpec = new TaskTransitionSpec(
-                        taskAnimationBackgroundColor,
-                        Set.of(ITYPE_EXTRA_NAVIGATION_BAR)
-                );
-                WindowManagerGlobal.getWindowManagerService()
-                        .setTaskTransitionSpec(customTaskAnimationSpec);
+                WindowManagerGlobal.getWindowManagerService().setTaskTransitionSpec(
+                        new TaskTransitionSpec(
+                                mLauncher.getColor(R.color.taskbar_background),
+                                Set.of(ITYPE_EXTRA_NAVIGATION_BAR)));
             }
         } catch (RemoteException e) {
             // This shouldn't happen but if it does task animations won't look good until the
@@ -349,6 +341,11 @@
     }
 
     @Override
+    protected boolean isInOverview() {
+        return mTaskbarLauncherStateController.isInOverview();
+    }
+
+    @Override
     public RecentsView getRecentsView() {
         return mLauncher.getOverviewPanel();
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 728c91f..bafd5b4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -125,8 +125,10 @@
     private static final int FLAG_SMALL_SCREEN = 1 << 13;
     private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
 
-    /** Flags where a UI could be over a slide in view, so the color override should be disabled. */
-    private static final int FLAGS_SLIDE_IN_VIEW_ICON_COLOR_OVERRIDE_DISABLED =
+    /**
+     * Flags where a UI could be over Taskbar surfaces, so the color override should be disabled.
+     */
+    private static final int FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED =
             FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
 
     private static final String NAV_BUTTONS_SEPARATE_WINDOW_TITLE = "Taskbar Nav Buttons";
@@ -148,8 +150,8 @@
     private final ViewGroup mStartContextualContainer;
     private final int mLightIconColor;
     private final int mDarkIconColor;
-    /** Color to use for navigation bar buttons, if a slide in view is visible. */
-    private final int mSlideInViewIconColor;
+    /** Color to use for navigation bar buttons, if they are on on a Taskbar surface background. */
+    private final int mOnBackgroundIconColor;
 
     private final AnimatedFloat mTaskbarNavButtonTranslationY = new AnimatedFloat(
             this::updateNavButtonTranslationY);
@@ -160,13 +162,18 @@
     // Used for System UI state updates that should translate the nav button for in-app display.
     private final AnimatedFloat mNavButtonInAppDisplayProgressForSysui = new AnimatedFloat(
             this::updateNavButtonInAppDisplayProgressForSysui);
+    /** Expected nav button dark intensity communicated via the framework. */
     private final AnimatedFloat mTaskbarNavButtonDarkIntensity = new AnimatedFloat(
-            this::updateNavButtonDarkIntensity);
-    private final AnimatedFloat mNavButtonDarkIntensityMultiplier = new AnimatedFloat(
-            this::updateNavButtonDarkIntensity);
-    /** Overrides the navigation button color to {@code mSlideInViewIconColor} when {@code 1}. */
-    private final AnimatedFloat mSlideInViewNavButtonColorOverride = new AnimatedFloat(
-            this::updateNavButtonDarkIntensity);
+            this::updateNavButtonColor);
+    /** {@code 1} if the Taskbar background color is fully opaque. */
+    private final AnimatedFloat mOnTaskbarBackgroundNavButtonColorOverride = new AnimatedFloat(
+            this::updateNavButtonColor);
+    /** {@code 1} if a Taskbar slide in overlay is visible over Taskbar. */
+    private final AnimatedFloat mSlideInViewVisibleNavButtonColorOverride = new AnimatedFloat(
+            this::updateNavButtonColor);
+    /** Disables the {@link #mOnBackgroundIconColor} override if {@code 0}. */
+    private final AnimatedFloat mOnBackgroundNavButtonColorOverrideMultiplier = new AnimatedFloat(
+            this::updateNavButtonColor);
     private final RotationButtonListener mRotationButtonListener = new RotationButtonListener();
 
     private final Rect mFloatingRotationButtonBounds = new Rect();
@@ -199,8 +206,7 @@
 
         mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
         mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
-        // Can precompute color since dark theme change recreates taskbar.
-        mSlideInViewIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor;
+        mOnBackgroundIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor;
     }
 
     /**
@@ -266,10 +272,15 @@
                 flags -> (flags & FLAG_IME_VISIBLE) != 0 && !isInKidsMode, AnimatedFloat.VALUE,
                 transForIme, defaultButtonTransY));
 
+        // Start at 1 because relevant flags are unset at init.
+        mOnBackgroundNavButtonColorOverrideMultiplier.value = 1;
         mPropertyHolders.add(new StatePropertyHolder(
-                mSlideInViewNavButtonColorOverride,
-                flags -> ((flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0)
-                        && ((flags & FLAGS_SLIDE_IN_VIEW_ICON_COLOR_OVERRIDE_DISABLED) == 0)));
+                mOnBackgroundNavButtonColorOverrideMultiplier,
+                flags -> (flags & FLAGS_ON_BACKGROUND_COLOR_OVERRIDE_DISABLED) == 0));
+
+        mPropertyHolders.add(new StatePropertyHolder(
+                mSlideInViewVisibleNavButtonColorOverride,
+                flags -> (flags & FLAG_SLIDE_IN_VIEW_VISIBLE) != 0));
 
         if (alwaysShowButtons) {
             initButtons(mNavButtonContainer, mEndContextualContainer,
@@ -569,9 +580,9 @@
         return mTaskbarNavButtonDarkIntensity;
     }
 
-    /** Use to determine whether to use the dark intensity requested by the underlying app */
-    public AnimatedFloat getNavButtonDarkIntensityMultiplier() {
-        return mNavButtonDarkIntensityMultiplier;
+    /** Use to override the nav button color with {@link #mOnBackgroundIconColor}. */
+    public AnimatedFloat getOnTaskbarBackgroundNavButtonColorOverride() {
+        return mOnTaskbarBackgroundNavButtonColorOverride;
     }
 
     /**
@@ -617,14 +628,20 @@
                 + inAppDisplayAdjustmentTranslationY);
     }
 
-    private void updateNavButtonDarkIntensity() {
-        float darkIntensity = mTaskbarNavButtonDarkIntensity.value
-                * mNavButtonDarkIntensityMultiplier.value;
-        ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
-        int iconColor = (int) argbEvaluator.evaluate(
-                darkIntensity, mLightIconColor, mDarkIconColor);
-        iconColor = (int) argbEvaluator.evaluate(
-                mSlideInViewNavButtonColorOverride.value, iconColor, mSlideInViewIconColor);
+    private void updateNavButtonColor() {
+        final ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
+        final int sysUiNavButtonIconColor = (int) argbEvaluator.evaluate(
+                mTaskbarNavButtonDarkIntensity.value,
+                mLightIconColor,
+                mDarkIconColor);
+        // Override the color from framework if nav buttons are over an opaque Taskbar surface.
+        final int iconColor = (int) argbEvaluator.evaluate(
+                mOnBackgroundNavButtonColorOverrideMultiplier.value
+                        * Math.max(
+                                mOnTaskbarBackgroundNavButtonColorOverride.value,
+                                mSlideInViewVisibleNavButtonColorOverride.value),
+                sysUiNavButtonIconColor,
+                mOnBackgroundIconColor);
         for (ImageView button : mAllButtons) {
             button.setImageTintList(ColorStateList.valueOf(iconColor));
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2864ac7..81389ab 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -432,11 +432,16 @@
         }
         LauncherAtom.ContainerInfo oldContainer = itemInfoBuilder.getContainerInfo();
 
+        LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
+                LauncherAtom.TaskBarContainer.newBuilder();
+        if (mControllers.uiController.isInOverview()) {
+            taskbarBuilder.setTaskSwitcherContainer(
+                    LauncherAtom.TaskSwitcherContainer.newBuilder());
+        }
+
         if (oldContainer.hasPredictedHotseatContainer()) {
             LauncherAtom.PredictedHotseatContainer predictedHotseat =
                     oldContainer.getPredictedHotseatContainer();
-            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
-                    LauncherAtom.TaskBarContainer.newBuilder();
 
             if (predictedHotseat.hasIndex()) {
                 taskbarBuilder.setIndex(predictedHotseat.getIndex());
@@ -449,8 +454,6 @@
                     .setTaskBarContainer(taskbarBuilder));
         } else if (oldContainer.hasHotseat()) {
             LauncherAtom.HotseatContainer hotseat = oldContainer.getHotseat();
-            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
-                    LauncherAtom.TaskBarContainer.newBuilder();
 
             if (hotseat.hasIndex()) {
                 taskbarBuilder.setIndex(hotseat.getIndex());
@@ -462,8 +465,6 @@
             LauncherAtom.FolderContainer.Builder folderBuilder = oldContainer.getFolder()
                     .toBuilder();
             LauncherAtom.HotseatContainer hotseat = folderBuilder.getHotseat();
-            LauncherAtom.TaskBarContainer.Builder taskbarBuilder =
-                    LauncherAtom.TaskBarContainer.newBuilder();
 
             if (hotseat.hasIndex()) {
                 taskbarBuilder.setIndex(hotseat.getIndex());
@@ -476,11 +477,11 @@
         } else if (oldContainer.hasAllAppsContainer()) {
             itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
                     .setAllAppsContainer(oldContainer.getAllAppsContainer().toBuilder()
-                            .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
+                            .setTaskbarContainer(taskbarBuilder)));
         } else if (oldContainer.hasPredictionContainer()) {
             itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
                     .setPredictionContainer(oldContainer.getPredictionContainer().toBuilder()
-                            .setTaskbarContainer(LauncherAtom.TaskBarContainer.newBuilder())));
+                            .setTaskbarContainer(taskbarBuilder)));
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index e00bc59..3375877 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -66,8 +66,6 @@
         paint.style = Paint.Style.FILL
 
         if (isTransientTaskbar) {
-            paint.color = context.getColor(R.color.transient_taskbar_background)
-
             val res = context.resources
             bottomMargin = res.getDimensionPixelSize(R.dimen.transient_taskbar_margin)
             shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 7c4071f..7c3d14d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -59,10 +59,9 @@
 
     // Initialized in init.
     private TaskbarControllers mControllers;
-    private AnimatedFloat mNavButtonDarkIntensityMultiplier;
+    private AnimatedFloat mOnBackgroundNavButtonColorIntensity;
 
     private float mLastSetBackgroundAlpha;
-    private boolean mIsBackgroundDrawnElsewhere;
 
     public TaskbarDragLayerController(TaskbarActivityContext activity,
             TaskbarDragLayer taskbarDragLayer) {
@@ -77,8 +76,8 @@
         mControllers = controllers;
         mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
 
-        mNavButtonDarkIntensityMultiplier = mControllers.navbarButtonsViewController
-                .getNavButtonDarkIntensityMultiplier();
+        mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
+                .getOnTaskbarBackgroundNavButtonColorOverride();
 
         mBgTaskbar.value = 1;
         mKeyguardBgTaskbar.value = 1;
@@ -152,7 +151,7 @@
         mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
         mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);
 
-        updateNavBarDarkIntensityMultiplier();
+        updateOnBackgroundNavButtonColorIntensity();
     }
 
     /**
@@ -165,7 +164,7 @@
     private void updateBackgroundOffset() {
         mTaskbarDragLayer.setTaskbarBackgroundOffset(mBgOffset.value);
 
-        updateNavBarDarkIntensityMultiplier();
+        updateOnBackgroundNavButtonColorIntensity();
     }
 
     @Override
@@ -174,23 +173,16 @@
     }
 
     /**
-     * Set if another controller is temporarily handling background drawing. In this case we:
-     * - Override our background alpha to be 0.
-     * - Keep the nav bar dark intensity assuming taskbar background is at full alpha.
+     * Set if another controller is temporarily handling background drawing. In this case we
+     * override our background alpha to be {@code 0}.
      */
     public void setIsBackgroundDrawnElsewhere(boolean isBackgroundDrawnElsewhere) {
-        mIsBackgroundDrawnElsewhere = isBackgroundDrawnElsewhere;
-        mBgOverride.updateValue(mIsBackgroundDrawnElsewhere ? 0 : 1);
-        updateNavBarDarkIntensityMultiplier();
+        mBgOverride.updateValue(isBackgroundDrawnElsewhere ? 0 : 1);
     }
 
-    private void updateNavBarDarkIntensityMultiplier() {
-        // Zero out the app-requested dark intensity when we're drawing our own background.
-        float effectiveBgAlpha = mLastSetBackgroundAlpha * (1 - mBgOffset.value);
-        if (mIsBackgroundDrawnElsewhere) {
-            effectiveBgAlpha = 1;
-        }
-        mNavButtonDarkIntensityMultiplier.updateValue(1 - effectiveBgAlpha);
+    private void updateOnBackgroundNavButtonColorIntensity() {
+        mOnBackgroundNavButtonColorIntensity.updateValue(
+                mLastSetBackgroundAlpha * (1 - mBgOffset.value));
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 5ac0570..4d163aa 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -406,6 +406,10 @@
         return mLauncherState != LauncherState.ALL_APPS;
     }
 
+    boolean isInOverview() {
+        return mLauncherState == LauncherState.OVERVIEW;
+    }
+
     private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
             boolean committed) {
         boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index b552e9b..033b075 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -166,6 +166,11 @@
         return true;
     }
 
+    /** Returns {@code true} if Taskbar is currently within overview. */
+    protected boolean isInOverview() {
+        return false;
+    }
+
     @CallSuper
     protected void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(String.format(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 0b275a8..3d5089f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -142,9 +142,8 @@
                     : R.drawable.ic_taskbar_all_apps_button));
             mAllAppsButton.setScaleX(mIsRtl ? -1 : 1);
             mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
-            mAllAppsButton.setForegroundTint(mActivityContext.getColor(isTransientTaskbar
-                            ? R.color.all_apps_button_color
-                            : R.color.all_apps_button_color_dark));
+            mAllAppsButton.setForegroundTint(
+                    mActivityContext.getColor(R.color.all_apps_button_color));
 
             if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
                 mTaskbarDivider = LayoutInflater.from(context).inflate(R.layout.taskbar_divider,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 2f8cae8..3143f23 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -349,7 +349,7 @@
                         MULTI_PROPERTY_VALUE, isStashed
                                 ? new float[] {croppedTransX}
                                 : new float[] {croppedTransX, 0}));
-                as.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
+                as.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
                         MULTI_PROPERTY_VALUE, isStashed
                                 ? new float[] {croppedTransY}
                                 : new float[] {croppedTransY, 0}));
@@ -503,7 +503,7 @@
 
                 setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
                         MULTI_PROPERTY_VALUE, hotseatIconCenter - childCenter, interpolator);
-                setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
+                setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
                         MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
             } else {
                 setter.setFloat(child, VIEW_TRANSLATE_X,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a53f08a..e268d1b 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -40,6 +40,9 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+import static com.android.launcher3.popup.SystemShortcut.INSTALL;
+import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.ALL_APPS_PAGE_PROGRESS_INDEX;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.MINUS_ONE_PAGE_PROGRESS_INDEX;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.WIDGETS_PAGE_PROGRESS_INDEX;
@@ -186,6 +189,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Predicate;
@@ -385,22 +390,30 @@
 
     @Override
     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
-        Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
-        if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
-            RecentsView recentsView = getOverviewPanel();
-            // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
-            List<SplitPositionOption> positions =
-                    recentsView.getPagedOrientationHandler().getSplitPositionOptions(
-                            mDeviceProfile);
-            List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
-            for (SplitPositionOption position : positions) {
-                splitShortcuts.add(getSplitSelectShortcutByPosition(position));
-            }
-            base = Stream.concat(base, splitShortcuts.stream());
+        // Order matters as it affects order of appearance in popup container
+        List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
+                APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
+        shortcuts.addAll(getSplitShortcuts());
+        shortcuts.add(WIDGETS);
+        shortcuts.add(INSTALL);
+        return shortcuts.stream();
+    }
+
+    private List<SystemShortcut.Factory<QuickstepLauncher>> getSplitShortcuts() {
+
+        if (!ENABLE_SPLIT_FROM_WORKSPACE.get() || !mDeviceProfile.isTablet) {
+            return Collections.emptyList();
         }
-        return Stream.concat(
-                Stream.of(mHotseatPredictionController),
-                Stream.concat(base, super.getSupportedShortcuts()));
+        RecentsView recentsView = getOverviewPanel();
+        // TODO(b/266482558): Pull it out of PagedOrentationHandler for split from workspace.
+        List<SplitPositionOption> positions =
+                recentsView.getPagedOrientationHandler().getSplitPositionOptions(
+                        mDeviceProfile);
+        List<SystemShortcut.Factory<QuickstepLauncher>> splitShortcuts = new ArrayList<>();
+        for (SplitPositionOption position : positions) {
+            splitShortcuts.add(getSplitSelectShortcutByPosition(position));
+        }
+        return splitShortcuts;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index 278a45a..ff3a292 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -22,6 +22,7 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
+import android.util.Log;
 import android.util.SparseArray;
 import android.widget.RemoteViews;
 
@@ -33,14 +34,14 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
+import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.function.BiConsumer;
 import java.util.function.IntConsumer;
@@ -50,6 +51,8 @@
  */
 public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
 
+    private static final String TAG = "QuickstepWidgetHolder";
+
     private static final UpdateKey<AppWidgetProviderInfo> KEY_PROVIDER_UPDATE =
             AppWidgetHostView::onUpdateProviderInfo;
     private static final UpdateKey<RemoteViews> KEY_VIEWS_UPDATE =
@@ -63,6 +66,8 @@
 
     private static AppWidgetHost sWidgetHost = null;
 
+    private final SparseArray<AppWidgetHostView> mViews = new SparseArray<>();
+
     private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;
 
     private final @NonNull IntConsumer mAppWidgetRemovedCallback;
@@ -71,15 +76,14 @@
     // Map to all pending updated keyed with appWidgetId;
     private final SparseArray<PendingUpdate> mPendingUpdateMap = new SparseArray<>();
 
-    @Thunk
-    QuickstepWidgetHolder(@NonNull Context context,
+    private QuickstepWidgetHolder(@NonNull Context context,
             @Nullable IntConsumer appWidgetRemovedCallback,
             @Nullable RemoteViews.InteractionHandler interactionHandler) {
         super(context, appWidgetRemovedCallback);
         mAppWidgetRemovedCallback = appWidgetRemovedCallback != null ? appWidgetRemovedCallback
                 : i -> {};
         mInteractionHandler = interactionHandler;
-        sHolders.add(this);
+        MAIN_EXECUTOR.execute(() -> sHolders.add(this));
     }
 
     @Override
@@ -92,7 +96,7 @@
                             sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
                     () -> MAIN_EXECUTOR.execute(() ->
                             sHolders.forEach(h -> h.mProviderChangedListeners.forEach(
-                            ProviderChangedListener::notifyWidgetProvidersChanged))),
+                                    ProviderChangedListener::notifyWidgetProvidersChanged))),
                     UI_HELPER_EXECUTOR.getLooper());
             if (!WidgetsModel.GO_DISABLE_WIDGETS) {
                 sWidgetHost.startListening();
@@ -107,11 +111,7 @@
         int count = mPendingUpdateMap.size();
         for (int i = 0; i < count; i++) {
             int widgetId = mPendingUpdateMap.keyAt(i);
-            QuickstepWidgetHolderListener listener = sListeners.get(widgetId);
-            if (listener == null) {
-                continue;
-            }
-            AppWidgetHostView view = listener.mView.get(this);
+            AppWidgetHostView view = mViews.get(widgetId);
             if (view == null) {
                 continue;
             }
@@ -131,7 +131,16 @@
         mPendingUpdateMap.clear();
     }
 
-    private <T> void addPendingAction(int widgetId, UpdateKey<T> key, T data) {
+    private <T> void onWidgetUpdate(int widgetId, UpdateKey<T> key, T data) {
+        if (isListening()) {
+            AppWidgetHostView view = mViews.get(widgetId);
+            if (view == null) {
+                return;
+            }
+            key.accept(view, data);
+            return;
+        }
+
         PendingUpdate pendingUpdate = mPendingUpdateMap.get(widgetId);
         if (pendingUpdate == null) {
             pendingUpdate = new PendingUpdate();
@@ -167,7 +176,11 @@
      */
     @Override
     public void destroy() {
-        sHolders.remove(this);
+        try {
+            MAIN_EXECUTOR.submit(() -> sHolders.remove(this)).get();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to remove self from holder list", e);
+        }
     }
 
     @Override
@@ -228,15 +241,16 @@
         }
         widgetView.setInteractionHandler(mInteractionHandler);
         widgetView.setAppWidget(appWidgetId, appWidget);
+        mViews.put(appWidgetId, widgetView);
 
         QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
         if (listener == null) {
-            listener = new QuickstepWidgetHolderListener(appWidgetId, this, widgetView);
+            listener = new QuickstepWidgetHolderListener(appWidgetId);
             sWidgetHost.setListener(appWidgetId, listener);
             sListeners.put(appWidgetId, listener);
-        } else {
-            listener.resetView(this, widgetView);
         }
+        RemoteViews remoteViews = listener.addHolder(this);
+        widgetView.updateAppWidget(remoteViews);
 
         return widgetView;
     }
@@ -247,31 +261,30 @@
     @Override
     public void clearViews() {
         for (int i = sListeners.size() - 1; i >= 0; i--) {
-            sListeners.valueAt(i).mView.remove(this);
+            sListeners.valueAt(i).mListeningHolders.remove(this);
         }
     }
 
     private static class QuickstepWidgetHolderListener
             implements AppWidgetHost.AppWidgetHostListener {
 
-        @NonNull
-        private final Map<QuickstepWidgetHolder, AppWidgetHostView> mView = new WeakHashMap<>();
+        // Static listeners should use a set that is backed by WeakHashMap to avoid memory leak
+        private final Set<QuickstepWidgetHolder> mListeningHolders = Collections.newSetFromMap(
+                new WeakHashMap<>());
 
         private final int mWidgetId;
 
-        @Nullable private RemoteViews mRemoteViews = null;
+        private @Nullable RemoteViews mRemoteViews;
 
-        QuickstepWidgetHolderListener(int widgetId, @NonNull QuickstepWidgetHolder holder,
-                @NonNull LauncherAppWidgetHostView view) {
+        QuickstepWidgetHolderListener(int widgetId) {
             mWidgetId = widgetId;
-            mView.put(holder, view);
         }
 
         @UiThread
-        public void resetView(@NonNull QuickstepWidgetHolder holder,
-                @NonNull AppWidgetHostView view) {
-            mView.put(holder, view);
-            view.updateAppWidget(mRemoteViews);
+        @Nullable
+        public RemoteViews addHolder(@NonNull QuickstepWidgetHolder holder) {
+            mListeningHolders.add(holder);
+            return mRemoteViews;
         }
 
         @Override
@@ -295,13 +308,8 @@
         }
 
         private <T> void executeOnMainExecutor(UpdateKey<T> key, T data) {
-            MAIN_EXECUTOR.execute(() -> mView.forEach((holder, view) -> {
-                if (holder.isListening()) {
-                    key.accept(view, data);
-                } else {
-                    holder.addPendingAction(mWidgetId, key, data);
-                }
-            }));
+            MAIN_EXECUTOR.execute(() -> mListeningHolders.forEach(holder ->
+                    holder.onWidgetUpdate(mWidgetId, key, data)));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
index 1630d0f..9982162 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarStashInputConsumer.java
@@ -52,7 +52,7 @@
     private final float mUnstashArea;
     private final float mScreenWidth;
 
-    private final int mTaskbarNavThresholdY;
+    private final int mTaskbarNavThreshold;
     private final boolean mIsTaskbarAllAppsOpen;
     private boolean mHasPassedTaskbarNavThreshold;
 
@@ -73,9 +73,7 @@
 
         Resources res = context.getResources();
         mUnstashArea = res.getDimensionPixelSize(R.dimen.taskbar_unstash_input_area);
-        int taskbarNavThreshold = res.getDimensionPixelSize(R.dimen.taskbar_nav_threshold);
-        int screenHeight = taskbarActivityContext.getDeviceProfile().heightPx;
-        mTaskbarNavThresholdY = screenHeight - taskbarNavThreshold;
+        mTaskbarNavThreshold = res.getDimensionPixelSize(R.dimen.taskbar_nav_threshold);
         mIsTaskbarAllAppsOpen =
                 mTaskbarActivityContext != null && mTaskbarActivityContext.isTaskbarAllAppsOpen();
 
@@ -157,7 +155,7 @@
                         if (mIsTransientTaskbar) {
                             float dY = mLastPos.y - mDownPos.y;
                             boolean passedTaskbarNavThreshold = dY < 0
-                                    && mLastPos.y < mTaskbarNavThresholdY;
+                                    && Math.abs(dY) >= mTaskbarNavThreshold;
 
                             if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
                                 mHasPassedTaskbarNavThreshold = true;
@@ -165,7 +163,7 @@
                             }
 
                             if (dY < 0) {
-                                dY = -OverScroll.dampedScroll(-dY, mTaskbarNavThresholdY);
+                                dY = -OverScroll.dampedScroll(-dY, mTaskbarNavThreshold);
                                 if (mTransitionCallback != null && !mIsTaskbarAllAppsOpen) {
                                     mTransitionCallback.onActionMove(dY);
                                 }
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 0389d07..79971de 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -88,6 +88,10 @@
 
     private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
 
+    private final Rect mTempSettingsBounds = new Rect();
+    private final Rect mTempInclusionBounds = new Rect();
+    private final Rect mTempExclusionBounds = new Rect();
+
     private TISBindHelper mTISBindHelper;
     private TISBinder mBinder;
 
@@ -131,9 +135,9 @@
                 !TextUtils.isEmpty(suwDeviceName)
                         ? suwDeviceName : getString(R.string.default_device_name)));
 
-        TextView tv = findViewById(R.id.navigation_settings);
-        tv.setTextColor(accentColor);
-        tv.setOnClickListener(v -> {
+        TextView settings = findViewById(R.id.navigation_settings);
+        settings.setTextColor(accentColor);
+        settings.setOnClickListener(v -> {
             try {
                 startActivityForResult(
                         Intent.parseUri(URI_SYSTEM_NAVIGATION_SETTING, 0), 0);
@@ -142,12 +146,41 @@
             }
         });
 
-        TextView hintTextView = findViewById(R.id.hint);
+        TextView hint = findViewById(R.id.hint);
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this);
         if (!dp.isGestureMode) {
-            hintTextView.setText(R.string.allset_button_hint);
+            hint.setText(R.string.allset_button_hint);
         }
-        hintTextView.setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+        hint.setAccessibilityDelegate(new SkipButtonAccessibilityDelegate());
+
+        View textContent = findViewById(R.id.text_content_view);
+        textContent.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                    mTempSettingsBounds.set(
+                            settings.getLeft(),
+                            settings.getTop(),
+                            settings.getRight(),
+                            settings.getBottom());
+                    mTempInclusionBounds.set(
+                            0,
+                            // Do not allow overlapping with the subtitle text
+                            subtitle.getBottom(),
+                            textContent.getWidth(),
+                            textContent.getHeight());
+                    mTempExclusionBounds.set(
+                            hint.getLeft(),
+                            hint.getTop(),
+                            hint.getRight(),
+                            hint.getBottom());
+
+                    Utilities.translateOverlappingView(
+                            settings,
+                            mTempSettingsBounds,
+                            mTempInclusionBounds,
+                            mTempExclusionBounds,
+                            Utilities.TRANSLATE_UP);
+                });
+
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
 
         mVibrator = getSystemService(Vibrator.class);
diff --git a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
index bd0ce34..b508484 100644
--- a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
+++ b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.graphics.Insets;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -34,6 +35,10 @@
 /** Root layout that TutorialFragment uses to intercept motion events. */
 public class RootSandboxLayout extends RelativeLayout {
 
+    private final Rect mTempStepIndicatorBounds = new Rect();
+    private final Rect mTempInclusionBounds = new Rect();
+    private final Rect mTempExclusionBounds = new Rect();
+
     private View mFeedbackView;
     private View mTutorialStepView;
     private View mSkipButton;
@@ -98,18 +103,23 @@
 
     private void updateTutorialStepViewTranslation(
             @NonNull View anchorView, boolean translateToRight) {
-        mTutorialStepView.setTranslationX(translateToRight
-                ? Math.min(
-                        // Translate to the right if the views are overlapping on large fonts and
-                        // display sizes.
-                        Math.max(0, anchorView.getRight() - mTutorialStepView.getLeft()),
-                        // Do not translate beyond the bounds of the container view.
-                        mFeedbackView.getWidth() - mTutorialStepView.getRight())
-                : Math.max(
-                        // Translate to the left if the views are overlapping on large fonts and
-                        // display sizes.
-                        Math.min(0, anchorView.getLeft() - mTutorialStepView.getRight()),
-                        // Do not translate beyond the bounds of the container view.
-                        -mTutorialStepView.getLeft()));
+        mTempStepIndicatorBounds.set(
+                mTutorialStepView.getLeft(),
+                mTutorialStepView.getTop(),
+                mTutorialStepView.getRight(),
+                mTutorialStepView.getBottom());
+        mTempInclusionBounds.set(0, 0, mFeedbackView.getWidth(), mFeedbackView.getHeight());
+        mTempExclusionBounds.set(
+                anchorView.getLeft(),
+                anchorView.getTop(),
+                anchorView.getRight(),
+                anchorView.getBottom());
+
+        Utilities.translateOverlappingView(
+                mTutorialStepView,
+                mTempStepIndicatorBounds,
+                mTempInclusionBounds,
+                mTempExclusionBounds,
+                translateToRight ? Utilities.TRANSLATE_RIGHT : Utilities.TRANSLATE_LEFT);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
index adea1a4..4ea7753 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
+++ b/quickstep/src/com/android/quickstep/views/FloatingWidgetBackgroundView.java
@@ -27,8 +27,10 @@
 import android.view.ViewOutlineProvider;
 import android.widget.RemoteViews.RemoteViewOutlineProvider;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.R;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.launcher3.widget.RoundedCornerEnforcement;
 
@@ -65,14 +67,20 @@
         setClipToOutline(true);
     }
 
-    void init(LauncherAppWidgetHostView hostView, View backgroundView, float finalRadius,
-            int fallbackBackgroundColor) {
+    void init(LauncherAppWidgetHostView hostView, @NonNull View backgroundView,
+            float finalRadius, int fallbackBackgroundColor) {
         mFinalRadius = finalRadius;
         mSourceView = backgroundView;
         mInitialOutlineRadius = getOutlineRadius(hostView, backgroundView);
         mIsUsingFallback = false;
         if (isSupportedDrawable(backgroundView.getForeground())) {
-            mOriginalForeground = backgroundView.getForeground();
+            if (backgroundView.getTag(R.id.saved_floating_widget_foreground) == null) {
+                mOriginalForeground = backgroundView.getForeground();
+                backgroundView.setTag(R.id.saved_floating_widget_foreground, mOriginalForeground);
+            } else {
+                mOriginalForeground = (Drawable) backgroundView.getTag(
+                        R.id.saved_floating_widget_foreground);
+            }
             mForegroundProperties.init(
                     mOriginalForeground.getConstantState().newDrawable().mutate());
             setForeground(mForegroundProperties.mDrawable);
@@ -82,7 +90,13 @@
             mSourceView.setForeground(clipPlaceholder);
         }
         if (isSupportedDrawable(backgroundView.getBackground())) {
-            mOriginalBackground = backgroundView.getBackground();
+            if (backgroundView.getTag(R.id.saved_floating_widget_background) == null) {
+                mOriginalBackground = backgroundView.getBackground();
+                backgroundView.setTag(R.id.saved_floating_widget_background, mOriginalBackground);
+            } else {
+                mOriginalBackground = (Drawable) backgroundView.getTag(
+                        R.id.saved_floating_widget_background);
+            }
             mBackgroundProperties.init(
                     mOriginalBackground.getConstantState().newDrawable().mutate());
             setBackground(mBackgroundProperties.mDrawable);
@@ -115,6 +129,10 @@
     }
 
     void recycle() {
+        if (mSourceView != null) {
+            mSourceView.setTag(R.id.saved_floating_widget_foreground, null);
+            mSourceView.setTag(R.id.saved_floating_widget_background, null);
+        }
         mSourceView = null;
         mOriginalForeground = null;
         mOriginalBackground = null;
diff --git a/res/color-night-v31/transient_taskbar_background.xml b/res/color-night-v31/taskbar_background.xml
similarity index 93%
rename from res/color-night-v31/transient_taskbar_background.xml
rename to res/color-night-v31/taskbar_background.xml
index 40f6494..8df1686 100644
--- a/res/color-night-v31/transient_taskbar_background.xml
+++ b/res/color-night-v31/taskbar_background.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 The Android Open Source Project
+<!-- Copyright (C) 2023 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -16,4 +16,3 @@
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
 </selector>
-
diff --git a/res/color-v31/taskbar_background.xml b/res/color-v31/taskbar_background.xml
index eaf676f..c2bcab8 100644
--- a/res/color-v31/taskbar_background.xml
+++ b/res/color-v31/taskbar_background.xml
@@ -14,5 +14,5 @@
      limitations under the License.
 -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-  <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
+  <item android:color="@android:color/system_neutral1_500" android:lStar="95" />
 </selector>
diff --git a/res/color-v31/transient_taskbar_background.xml b/res/color-v31/transient_taskbar_background.xml
deleted file mode 100644
index bce947d..0000000
--- a/res/color-v31/transient_taskbar_background.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2022 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.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-  <item android:color="@android:color/system_neutral1_500" android:lStar="95" />
-</selector>
-
diff --git a/res/drawable/bg_work_apps_paused_action_button.xml b/res/drawable/bg_work_apps_paused_action_button.xml
index 74d4693..57d1ae5 100644
--- a/res/drawable/bg_work_apps_paused_action_button.xml
+++ b/res/drawable/bg_work_apps_paused_action_button.xml
@@ -21,11 +21,12 @@
             <solid android:color="@android:color/white" />
         </shape>
     </item>
-
     <item android:id="@android:id/background">
         <shape android:shape="rectangle">
-            <solid android:color="?android:attr/colorControlHighlight" />
             <corners android:radius="@dimen/rounded_button_radius" />
+            <stroke
+                android:width="@dimen/work_apps_paused_button_stroke"
+                android:color="@color/work_turn_on_stroke" />
         </shape>
     </item>
 </ripple>
\ No newline at end of file
diff --git a/res/drawable/ic_split_horizontal.xml b/res/drawable/ic_split_horizontal.xml
index ee710d0..2efd2b9 100644
--- a/res/drawable/ic_split_horizontal.xml
+++ b/res/drawable/ic_split_horizontal.xml
@@ -1,9 +1,9 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="20dp"
-    android:height="16dp"
-    android:viewportWidth="20"
-    android:viewportHeight="16">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
   <path
-      android:pathData="M18,14L13,14L13,2L18,2L18,14ZM20,14L20,2C20,0.9 19.1,-0 18,-0L13,-0C11.9,-0 11,0.9 11,2L11,14C11,15.1 11.9,16 13,16L18,16C19.1,16 20,15.1 20,14ZM7,14L2,14L2,2L7,2L7,14ZM9,14L9,2C9,0.9 8.1,-0 7,-0L2,-0C0.9,-0 -0,0.9 -0,2L-0,14C-0,15.1 0.9,16 2,16L7,16C8.1,16 9,15.1 9,14Z"
+      android:pathData="M4,6L9,6L9,18L4,18L4,6ZM2,6L2,18C2,19.1 2.9,20 4,20L9,20C10.1,20 11,19.1 11,18L11,6C11,4.9 10.1,4 9,4L4,4C2.9,4 2,4.9 2,6ZM15,6L20,6L20,18L15,18L15,6ZM13,6L13,18C13,19.1 13.9,20 15,20L20,20C21.1,20 22,19.1 22,18L22,6C22,4.9 21.1,4 20,4L15,4C13.9,4 13,4.9 13,6Z"
       android:fillColor="#000000"/>
 </vector>
diff --git a/res/layout/system_shortcut_icons_container.xml b/res/layout/system_shortcut_icons_container.xml
index ee104d9..dc4fdb3 100644
--- a/res/layout/system_shortcut_icons_container.xml
+++ b/res/layout/system_shortcut_icons_container.xml
@@ -22,11 +22,4 @@
     android:orientation="horizontal"
     android:gravity="end|center_vertical"
     android:background="@drawable/single_item_primary"
-    android:elevation="@dimen/deep_shortcuts_elevation"
-    android:clipToPadding="true">
-
-    <Space android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:id="@+id/separator"/>
-</LinearLayout>
+    android:elevation="@dimen/deep_shortcuts_elevation"/>
diff --git a/res/layout/system_shortcut_icons_container_material_u.xml b/res/layout/system_shortcut_icons_container_material_u.xml
index afd11e6..70950ba 100644
--- a/res/layout/system_shortcut_icons_container_material_u.xml
+++ b/res/layout/system_shortcut_icons_container_material_u.xml
@@ -23,10 +23,4 @@
     android:orientation="horizontal"
     android:gravity="end|center_vertical"
     android:background="@drawable/popup_background_material_u"
-    android:elevation="@dimen/deep_shortcuts_elevation">
-
-    <Space android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:id="@+id/separator"/>
-</LinearLayout>
+    android:elevation="@dimen/deep_shortcuts_elevation"/>
diff --git a/res/layout/system_shortcut_rows_container_material_u.xml b/res/layout/system_shortcut_rows_container.xml
similarity index 100%
rename from res/layout/system_shortcut_rows_container_material_u.xml
rename to res/layout/system_shortcut_rows_container.xml
diff --git a/res/layout/system_shortcut_spacer.xml b/res/layout/system_shortcut_spacer.xml
new file mode 100644
index 0000000..60ea9ec
--- /dev/null
+++ b/res/layout/system_shortcut_spacer.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<Space
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
+    android:layout_weight="1"
+    android:id="@+id/shortcut_spacer"/>
\ No newline at end of file
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index f614d9b..52c5a49 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -21,7 +21,7 @@
 
     <TextView
         style="@style/PrimaryHeadline"
-        android:textColor="?attr/workProfileOverlayTextColor"
+        android:textColor="?android:attr/textColorPrimary"
         android:id="@+id/work_apps_paused_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -45,7 +45,7 @@
         android:layout_width="wrap_content"
         android:layout_height="@dimen/rounded_button_height"
         android:id="@+id/enable_work_apps"
-        android:textColor="?attr/workProfileOverlayTextColor"
+        android:textColor="?android:attr/textColorPrimary"
         android:text="@string/work_apps_enable_btn_text"
         android:textAlignment="center"
         android:background="@drawable/bg_work_apps_paused_action_button"
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index 2c1bc90..f331361 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -24,4 +24,6 @@
     <color name="home_settings_thumb_off_color">@android:color/system_neutral2_300</color>
     <color name="home_settings_track_on_color">@android:color/system_accent2_700</color>
     <color name="home_settings_track_off_color">@android:color/system_neutral1_700</color>
+
+    <color name="all_apps_button_color">@android:color/system_neutral2_200</color>
 </resources>
\ No newline at end of file
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index 4ba77fa..17fe419 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -17,5 +17,5 @@
 -->
 
 <resources>
-    <color name="all_apps_button_color">@color/all_apps_button_color_dark</color>
+    <color name="all_apps_button_color">#BFC8CC</color>
 </resources>
\ No newline at end of file
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index f87d9fc..054fe47 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -62,8 +62,7 @@
     <color name="preload_icon_accent_color_dark">@android:color/system_accent1_300</color>
     <color name="preload_icon_background_color_dark">@android:color/system_neutral2_700</color>
 
-    <color name="all_apps_button_color_light">@android:color/system_neutral2_700</color>
-    <color name="all_apps_button_color_dark">@android:color/system_neutral2_200</color>
+    <color name="all_apps_button_color">@android:color/system_neutral2_700</color>
 
     <color name="widget_picker_background_selected">@android:color/system_accent2_100</color>
 </resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f70937a..96938ca 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -50,7 +50,6 @@
     <attr name="folderTextColor" format="color" />
     <attr name="folderHintColor" format="color" />
     <attr name="isFolderDarkText" format="boolean" />
-    <attr name="workProfileOverlayTextColor" format="color" />
     <attr name="workspaceAccentColor" format="color" />
     <attr name="dropTargetHoverTextColor" format="color" />
     <attr name="preloadIconAccentColor" format="color" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index ef7bf91..8788557 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -80,12 +80,12 @@
     <color name="workspace_accent_color_light">#ff8df5e3</color>
     <color name="workspace_accent_color_dark">#ff3d665f</color>
 
-    <color name="all_apps_button_color">@color/all_apps_button_color_light</color>
-    <color name="all_apps_button_color_light">#40484B</color>
-    <color name="all_apps_button_color_dark">#BFC8CC</color>
+    <color name="all_apps_button_color">#40484B</color>
 
     <color name="preload_icon_accent_color_light">#00668B</color>
     <color name="preload_icon_background_color_light">#B5CAD7</color>
     <color name="preload_icon_accent_color_dark">#4BB6E8</color>
     <color name="preload_icon_background_color_dark">#40484D</color>
+
+    <color name="work_turn_on_stroke">?android:attr/colorAccent</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 82758bf..aa84d2b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -155,6 +155,7 @@
     <dimen name="work_edu_card_margin">16dp</dimen>
     <dimen name="work_edu_card_radius">16dp</dimen>
     <dimen name="work_edu_card_bottom_margin">26dp</dimen>
+    <dimen name="work_apps_paused_button_stroke">1dp</dimen>
 
     <dimen name="work_card_margin">24dp</dimen>
     <!-- (x) icon button inside work edu card -->
diff --git a/res/values/id.xml b/res/values/id.xml
index 375750f..dc81944 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -38,4 +38,7 @@
     <item type="id" name="cache_entry_tag_id" />
 
     <item type="id" name="saved_clip_children_tag_id" />
+
+    <item type="id" name="saved_floating_widget_foreground" />
+    <item type="id" name="saved_floating_widget_background" />
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 474a289..65d215f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -59,7 +59,6 @@
         <item name="folderHintColor">@color/folder_hint_text_color_dark</item>
         <item name="loadingIconColor">#CCFFFFFF</item>
         <item name="iconOnlyShortcutColor">?android:attr/textColorSecondary</item>
-        <item name="workProfileOverlayTextColor">#FF212121</item>
         <item name="eduHalfSheetBGColor">?android:attr/colorAccent</item>
         <item name="workspaceAccentColor">@color/workspace_accent_color_light</item>
         <item name="dropTargetHoverTextColor">@color/workspace_text_color_dark</item>
@@ -114,7 +113,6 @@
         <item name="isMainColorDark">true</item>
         <item name="loadingIconColor">#99FFFFFF</item>
         <item name="iconOnlyShortcutColor">#B3FFFFFF</item>
-        <item name="workProfileOverlayTextColor">@android:color/white</item>
         <item name="eduHalfSheetBGColor">#DD000000</item>
         <item name="overviewScrimColor">@color/overview_scrim_dark</item>
         <item name="preloadIconAccentColor">@color/preload_icon_accent_color_dark</item>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 731ad74..38b0e08 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -65,6 +65,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
+import com.android.launcher3.celllayout.ReorderAlgorithm;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
@@ -115,7 +116,7 @@
     final PointF mTmpPointF = new PointF();
 
     protected GridOccupancy mOccupied;
-    protected GridOccupancy mTmpOccupied;
+    public GridOccupancy mTmpOccupied;
 
     private OnTouchListener mInterceptTouchListener;
 
@@ -194,7 +195,7 @@
 
     private final ArrayList<View> mIntersectingViews = new ArrayList<>();
     private final Rect mOccupiedRect = new Rect();
-    private final int[] mDirectionVector = new int[2];
+    public final int[] mDirectionVector = new int[2];
 
     ItemConfiguration mPreviousSolution = null;
     private static final int INVALID_DIRECTION = -100;
@@ -1246,8 +1247,8 @@
      * @return The X, Y cell of a vacant area that can contain this object,
      *         nearest the requested location.
      */
-    int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX,
-            int spanY, int[] result, int[] resultSpan) {
+    public int[] findNearestVacantArea(int pixelX, int pixelY, int minSpanX, int minSpanY,
+            int spanX, int spanY, int[] result, int[] resultSpan) {
         return findNearestArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, false,
                 result, resultSpan);
     }
@@ -1380,6 +1381,10 @@
         return bestXY;
     }
 
+    public GridOccupancy getOccupied() {
+        return mOccupied;
+    }
+
     private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
         mTmpOccupied.clear();
 
@@ -1655,38 +1660,8 @@
         }
     }
 
-    /**
-     * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
-     * any other item in the way.
-     *
-     * @param pixelX X coordinate in pixels in the screen
-     * @param pixelY Y coordinate in pixels in the screen
-     * @param spanX horizontal cell span
-     * @param spanY vertical cell span
-     * @return the configuration that represents the found reorder
-     */
-    ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY) {
-        ItemConfiguration solution = new ItemConfiguration();
-        int[] result = new int[2];
-        int[] resultSpan = new int[2];
-        findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
-                resultSpan);
-        if (result[0] >= 0 && result[1] >= 0) {
-            copyCurrentStateToSolution(solution, false);
-            solution.cellX = result[0];
-            solution.cellY = result[1];
-            solution.spanX = resultSpan[0];
-            solution.spanY = resultSpan[1];
-            solution.isSolution = true;
-        } else {
-            solution.isSolution = false;
-        }
-        return solution;
-    }
-
     // For a given cell and span, fetch the set of views intersecting the region.
-    private void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
+    public void getViewsIntersectingRegion(int cellX, int cellY, int spanX, int spanY,
             View dragView, Rect boundingRect, ArrayList<View> intersectingViews) {
         if (boundingRect != null) {
             boundingRect.set(cellX, cellY, cellX + spanX, cellY + spanY);
@@ -1711,7 +1686,7 @@
         }
     }
 
-    boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
+    public boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
             View dragView, int[] result) {
         result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
         getViewsIntersectingRegion(result[0], result[1], spanX, spanY, dragView, null,
@@ -2257,7 +2232,7 @@
     those cells. Instead we use some heuristics to often lock the vector to up, down, left
     or right, which helps make pushing feel right.
     */
-    private void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
+    public void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
             int spanY, View dragView, int[] resultDirection) {
 
         //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
@@ -2349,7 +2324,7 @@
         return success;
     }
 
-    private boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
+    public boolean rearrangementExists(int cellX, int cellY, int spanX, int spanY, int[] direction,
             View ignoreView, ItemConfiguration solution) {
         // Return early if get invalid cell positions
         if (cellX < 0 || cellY < 0) return false;
@@ -2405,55 +2380,18 @@
         return true;
     }
 
+    public ReorderAlgorithm createReorderAlgorithm() {
+        return new ReorderAlgorithm(this);
+    }
+
     protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
             ItemConfiguration solution) {
-        return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
-                direction, dragView, decX, solution);
+        return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
+                spanX, spanY, direction, dragView, decX, solution);
     }
 
-    protected ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
-            ItemConfiguration solution) {
-        // Copy the current state into the solution. This solution will be manipulated as necessary.
-        copyCurrentStateToSolution(solution, false);
-        // Copy the current occupied array into the temporary occupied array. This array will be
-        // manipulated as necessary to find a solution.
-        mOccupied.copyTo(mTmpOccupied);
-
-        // We find the nearest cell into which we would place the dragged item, assuming there's
-        // nothing in its way.
-        int result[] = new int[2];
-        result = findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
-
-        boolean success;
-        // First we try the exact nearest position of the item being dragged,
-        // we will then want to try to move this around to other neighbouring positions
-        success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
-                solution);
-
-        if (!success) {
-            // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
-            // x, then 1 in y etc.
-            if (spanX > minSpanX && (minSpanY == spanY || decX)) {
-                return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX - 1,
-                        spanY, direction, dragView, false, solution);
-            } else if (spanY > minSpanY) {
-                return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX,
-                        spanY - 1, direction, dragView, true, solution);
-            }
-            solution.isSolution = false;
-        } else {
-            solution.isSolution = true;
-            solution.cellX = result[0];
-            solution.cellY = result[1];
-            solution.spanX = spanX;
-            solution.spanY = spanY;
-        }
-        return solution;
-    }
-
-    protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+    public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
@@ -2469,35 +2407,6 @@
     }
 
     /**
-     * Returns a "reorder" if there is empty space without rearranging anything.
-     *
-     * @param pixelX X coordinate in pixels in the screen
-     * @param pixelY Y coordinate in pixels in the screen
-     * @param spanX horizontal cell span
-     * @param spanY vertical cell span
-     * @param dragView view being dragged in reorder
-     * @return the configuration that represents the found reorder
-     */
-    public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
-            int spanY, View dragView) {
-        int[] result = new int[2];
-        if (isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView, result)) {
-            result[0] = result[1] = -1;
-        }
-        ItemConfiguration solution = new ItemConfiguration();
-        copyCurrentStateToSolution(solution, false);
-        solution.isSolution = result[0] != -1;
-        if (!solution.isSolution) {
-            return solution;
-        }
-        solution.cellX = result[0];
-        solution.cellY = result[1];
-        solution.spanX = spanX;
-        solution.spanY = spanY;
-        return solution;
-    }
-
-    /**
      * When the user drags an Item in the workspace sometimes we need to move the items already in
      * the workspace to make space for the new item, this function return a solution for that
      * reorder.
@@ -2514,29 +2423,8 @@
      */
     public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
             int spanX, int spanY, View dragView) {
-        getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView, mDirectionVector);
-
-        ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY,
-                dragView);
-
-        // Find a solution involving pushing / displacing any items in the way
-        ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
-                spanX,  spanY, mDirectionVector, dragView,  true,  new ItemConfiguration());
-
-        // We attempt the approach which doesn't shuffle views at all
-        ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(pixelX, pixelY, minSpanX,
-                minSpanY, spanX, spanY);
-
-        // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
-        // favor a solution in which the item is not resized, but
-        if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) {
-            return swapSolution;
-        } else if (closestSpaceSolution.isSolution) {
-            return closestSpaceSolution;
-        } else if (dropInPlaceSolution.isSolution) {
-            return dropInPlaceSolution;
-        }
-        return null;
+        return createReorderAlgorithm().calculateReorder(pixelX, pixelY, minSpanX, minSpanY,
+                spanX, spanY, dragView);
     }
 
     int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
@@ -2588,7 +2476,7 @@
      *             {@link MODE_ON_DROP}, {@link MODE_ON_DROP_EXTERNAL}, {@link  MODE_ACCEPT_DROP}
      *             defined in {@link CellLayout}.
      */
-    void performReorder(ItemConfiguration solution, View dragView, int mode) {
+    public void performReorder(ItemConfiguration solution, View dragView, int mode) {
         if (mode == MODE_SHOW_REORDER_HINT) {
             beginOrAdjustReorderPreviewAnimations(solution, dragView,
                     ReorderPreviewAnimation.MODE_HINT);
@@ -2634,38 +2522,41 @@
         return mItemPlacementDirty;
     }
 
-    static class ItemConfiguration extends CellAndSpan {
-        final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
+    /**
+     * Represents the solution to a reorder of items in the Workspace.
+     */
+    public static class ItemConfiguration extends CellAndSpan {
+        public final ArrayMap<View, CellAndSpan> map = new ArrayMap<>();
         private final ArrayMap<View, CellAndSpan> savedMap = new ArrayMap<>();
-        final ArrayList<View> sortedViews = new ArrayList<>();
-        ArrayList<View> intersectingViews;
-        boolean isSolution = false;
+        public final ArrayList<View> sortedViews = new ArrayList<>();
+        public ArrayList<View> intersectingViews;
+        public boolean isSolution = false;
 
-        void save() {
+        public void save() {
             // Copy current state into savedMap
             for (View v: map.keySet()) {
                 savedMap.get(v).copyFrom(map.get(v));
             }
         }
 
-        void restore() {
+        public void restore() {
             // Restore current state from savedMap
             for (View v: savedMap.keySet()) {
                 map.get(v).copyFrom(savedMap.get(v));
             }
         }
 
-        void add(View v, CellAndSpan cs) {
+        public void add(View v, CellAndSpan cs) {
             map.put(v, cs);
             savedMap.put(v, new CellAndSpan());
             sortedViews.add(v);
         }
 
-        int area() {
+        public int area() {
             return spanX * spanY;
         }
 
-        void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
+        public void getBoundingRectForViews(ArrayList<View> views, Rect outRect) {
             boolean first = true;
             for (View v: views) {
                 CellAndSpan c = map.get(v);
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
index 6a518a7..12cb35d 100644
--- a/src/com/android/launcher3/MultipageCellLayout.java
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -23,11 +23,11 @@
 import android.view.View;
 
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
+import com.android.launcher3.celllayout.MulticellReorderAlgorithm;
+import com.android.launcher3.celllayout.ReorderAlgorithm;
 import com.android.launcher3.util.CellAndSpan;
 import com.android.launcher3.util.GridOccupancy;
 
-import java.util.function.Supplier;
-
 /**
  * CellLayout that simulates a split in the middle for use in foldable devices.
  */
@@ -36,8 +36,6 @@
     private final Drawable mLeftBackground;
     private final Drawable mRightBackground;
 
-    private View mSeam;
-
     private boolean mSeamWasAdded = false;
 
     public MultipageCellLayout(Context context) {
@@ -62,7 +60,6 @@
 
         mCountX = deviceProfile.inv.numColumns * 2;
         mCountY = deviceProfile.inv.numRows;
-        mSeam = new View(getContext());
         setGridSize(mCountX, mCountY);
     }
 
@@ -74,90 +71,18 @@
             cellX++;
         }
         int finalCellX = cellX;
-        return simulateSeam(
+        return ((MulticellReorderAlgorithm) createReorderAlgorithm()).simulateSeam(
                 () -> super.createAreaForResize(finalCellX, cellY, spanX, spanY, dragView,
                         direction, commit));
     }
 
     @Override
-    ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
-            int spanX, int spanY) {
-        return removeSeamFromSolution(simulateSeam(
-                () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
-                        spanY)));
+    public ReorderAlgorithm createReorderAlgorithm() {
+        return new MulticellReorderAlgorithm(this);
     }
 
     @Override
-    protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
-            ItemConfiguration solution) {
-        return removeSeamFromSolution(simulateSeam(
-                () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
-                        direction, dragView, decX, solution)));
-    }
-
-    @Override
-    public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX, int spanY,
-            View dragView) {
-        return removeSeamFromSolution(simulateSeam(
-                () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
-    }
-
-    void addSeam() {
-        CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mCountX / 2, 0, 1, mCountY);
-        mSeamWasAdded = true;
-        lp.canReorder = false;
-        mCountX++;
-        mShortcutsAndWidgets.addViewInLayout(mSeam, lp);
-        mOccupied = createGridOccupancyWithSeam(mOccupied);
-        mTmpOccupied = new GridOccupancy(mCountX, mCountY);
-    }
-
-    void removeSeam() {
-        mCountX--;
-        mShortcutsAndWidgets.removeViewInLayout(mSeam);
-        mTmpOccupied = new GridOccupancy(mCountX, mCountY);
-        mSeamWasAdded = false;
-    }
-
-    protected <T> T simulateSeam(Supplier<T> f) {
-        if (mSeamWasAdded) {
-            return f.get();
-        }
-        GridOccupancy auxGrid = mOccupied;
-        addSeam();
-        T res = f.get();
-        removeSeam();
-        mOccupied = auxGrid;
-        return res;
-    }
-
-    private ItemConfiguration removeSeamFromSolution(ItemConfiguration solution) {
-        solution.map.forEach((view, cell) -> cell.cellX = cell.cellX > mCountX / 2
-                ? cell.cellX - 1 : cell.cellX);
-        solution.cellX = solution.cellX > mCountX / 2 ? solution.cellX - 1 : solution.cellX;
-        return solution;
-    }
-
-
-
-    GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) {
-        GridOccupancy grid = new GridOccupancy(getCountX(), getCountY());
-        for (int x = 0; x < getCountX(); x++) {
-            for (int y = 0; y < getCountY(); y++) {
-                int offset = x >= getCountX() / 2 ? 1 : 0;
-                if (x == getCountX() / 2) {
-                    grid.cells[x][y] = true;
-                } else {
-                    grid.cells[x][y] = gridOccupancy.cells[x - offset][y];
-                }
-            }
-        }
-        return grid;
-    }
-
-    @Override
-    protected void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
+    public void copyCurrentStateToSolution(ItemConfiguration solution, boolean temp) {
         int childCount = mShortcutsAndWidgets.getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = mShortcutsAndWidgets.getChildAt(i);
@@ -196,4 +121,24 @@
         mLeftBackground.setBounds(rect.left, rect.top, rect.right / 2 - 20, rect.bottom);
         mRightBackground.setBounds(rect.right / 2 + 20, rect.top, rect.right, rect.bottom);
     }
+
+    public void setCountX(int countX) {
+        mCountX = countX;
+    }
+
+    public void setCountY(int countY) {
+        mCountY = countY;
+    }
+
+    public void setOccupied(GridOccupancy occupied) {
+        mOccupied = occupied;
+    }
+
+    public boolean isSeamWasAdded() {
+        return mSeamWasAdded;
+    }
+
+    public void setSeamWasAdded(boolean seamWasAdded) {
+        mSeamWasAdded = seamWasAdded;
+    }
 }
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index c7431ed..e3bce87 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1370,8 +1370,14 @@
                 int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker,
                         mActivePointerId);
                 float delta = primaryDirection - mDownMotionPrimary;
-                int pageOrientedSize = (int) (mOrientationHandler.getMeasuredSize(
-                        getPageAt(mCurrentPage))
+
+                View current = getPageAt(mCurrentPage);
+                if (current == null) {
+                    Log.e(TAG, "current page was null. this should not happen.");
+                    return true;
+                }
+
+                int pageOrientedSize = (int) (mOrientationHandler.getMeasuredSize(current)
                         * mOrientationHandler.getPrimaryScale(this));
                 boolean isSignificantMove = isSignificantMove(Math.abs(delta), pageOrientedSize);
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 59327dc..0fbaecb 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -65,6 +65,7 @@
 import android.view.animation.Interpolator;
 
 import androidx.annotation.ChecksSdkIntAtLeast;
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.core.graphics.ColorUtils;
 
@@ -136,6 +137,14 @@
     @Deprecated
     public static final boolean IS_DEBUG_DEVICE = BuildConfig.IS_DEBUG_DEVICE;
 
+    public static final int TRANSLATE_UP = 0;
+    public static final int TRANSLATE_DOWN = 1;
+    public static final int TRANSLATE_LEFT = 2;
+    public static final int TRANSLATE_RIGHT = 3;
+
+    @IntDef({TRANSLATE_UP, TRANSLATE_DOWN, TRANSLATE_LEFT, TRANSLATE_RIGHT})
+    public @interface AdjustmentDirection{}
+
     /**
      * Returns true if theme is dark.
      */
@@ -731,4 +740,63 @@
                 matrixValues[Matrix.MTRANS_X], matrixValues[Matrix.MTRANS_Y]
         ));
     }
+
+    /**
+     * Translates the {@code targetView} so that it overlaps with {@code exclusionBounds} as little
+     * as possible, while remaining within {@code inclusionBounds}.
+     * <p>
+     * {@code inclusionBounds} will always take precedence over {@code exclusionBounds}, so if
+     * {@code targetView} needs to be translated outside of {@code inclusionBounds} to fully fix an
+     * overlap with {@code exclusionBounds}, then {@code targetView} will only be translated up to
+     * the border of {@code inclusionBounds}.
+     * <p>
+     * Note: {@code targetViewBounds}, {@code inclusionBounds} and {@code exclusionBounds} must all
+     * be in relation to the same reference point on screen.
+     * <p>
+     * @param targetView the view being translated
+     * @param targetViewBounds the bounds of the {@code targetView}
+     * @param inclusionBounds the bounds the {@code targetView} absolutely must stay within
+     * @param exclusionBounds the bounds to try to move the {@code targetView} away from
+     * @param adjustmentDirection the translation direction that should be attempted to fix an
+     *                            overlap
+     */
+    public static void translateOverlappingView(
+            @NonNull View targetView,
+            @NonNull Rect targetViewBounds,
+            @NonNull Rect inclusionBounds,
+            @NonNull Rect exclusionBounds,
+            @AdjustmentDirection int adjustmentDirection) {
+        switch (adjustmentDirection) {
+            case TRANSLATE_RIGHT:
+                targetView.setTranslationX(Math.min(
+                        // Translate to the right if the view is overlapping on the left.
+                        Math.max(0, exclusionBounds.right - targetViewBounds.left),
+                        // Do not translate beyond the inclusion bounds.
+                        inclusionBounds.right - targetViewBounds.right));
+                break;
+            case TRANSLATE_LEFT:
+                targetView.setTranslationX(Math.max(
+                        // Translate to the left if the view is overlapping on the right.
+                        Math.min(0, exclusionBounds.left - targetViewBounds.right),
+                        // Do not translate beyond the inclusion bounds.
+                        inclusionBounds.left - targetViewBounds.left));
+                break;
+            case TRANSLATE_DOWN:
+                targetView.setTranslationY(Math.min(
+                        // Translate downwards if the view is overlapping on the top.
+                        Math.max(0, exclusionBounds.bottom - targetViewBounds.top),
+                        // Do not translate beyond the inclusion bounds.
+                        inclusionBounds.bottom - targetViewBounds.bottom));
+                break;
+            case TRANSLATE_UP:
+                targetView.setTranslationY(Math.max(
+                        // Translate upwards if the view is overlapping on the bottom.
+                        Math.min(0, exclusionBounds.top - targetViewBounds.bottom),
+                        // Do not translate beyond the inclusion bounds.
+                        inclusionBounds.top - targetViewBounds.top));
+                break;
+            default:
+                // No-Op
+        }
+    }
 }
diff --git a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
new file mode 100644
index 0000000..cb12161
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout;
+
+import android.view.View;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.MultipageCellLayout;
+import com.android.launcher3.util.GridOccupancy;
+
+import java.util.function.Supplier;
+
+/**
+ * Variant of ReorderAlgorithm which simulates a foldable screen and adds a seam in the middle
+ * to prevent items to be placed in the middle.
+ */
+public class MulticellReorderAlgorithm extends ReorderAlgorithm {
+
+    private final View mSeam;
+
+    public MulticellReorderAlgorithm(CellLayout cellLayout) {
+        super(cellLayout);
+        mSeam = new View(cellLayout.getContext());
+    }
+
+    private CellLayout.ItemConfiguration removeSeamFromSolution(
+            CellLayout.ItemConfiguration solution) {
+        solution.map.forEach((view, cell) -> cell.cellX =
+                cell.cellX > mCellLayout.getCountX() / 2 ? cell.cellX - 1 : cell.cellX);
+        solution.cellX =
+                solution.cellX > mCellLayout.getCountX() / 2 ? solution.cellX - 1 : solution.cellX;
+        return solution;
+    }
+
+    @Override
+    public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
+            int minSpanX, int minSpanY,
+            int spanX, int spanY) {
+        return removeSeamFromSolution(simulateSeam(
+                () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
+                        spanY)));
+    }
+
+    @Override
+    public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
+            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
+            CellLayout.ItemConfiguration solution) {
+        return removeSeamFromSolution(simulateSeam(
+                () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
+                        direction, dragView, decX, solution)));
+    }
+
+    @Override
+    public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
+            int spanY,
+            View dragView) {
+        return removeSeamFromSolution(simulateSeam(
+                () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
+    }
+
+    void addSeam() {
+        MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
+        mcl.setSeamWasAdded(true);
+        CellLayoutLayoutParams lp = new CellLayoutLayoutParams(mcl.getCountX() / 2, 0, 1,
+                mcl.getCountY());
+        lp.canReorder = false;
+        mcl.setCountX(mcl.getCountX() + 1);
+        mcl.getShortcutsAndWidgets().addViewInLayout(mSeam, lp);
+        mcl.setOccupied(createGridOccupancyWithSeam(mcl.getOccupied()));
+        mcl.mTmpOccupied = new GridOccupancy(mcl.getCountX(), mcl.getCountY());
+    }
+
+    void removeSeam() {
+        MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
+        mcl.setCountX(mcl.getCountX() - 1);
+        mcl.getShortcutsAndWidgets().removeViewInLayout(mSeam);
+        mcl.mTmpOccupied = new GridOccupancy(mcl.getCountX(), mcl.getCountY());
+        mcl.setSeamWasAdded(false);
+    }
+
+    /**
+     * The function supplied here will execute while the CellLayout has a simulated seam added.
+     * @param f function to run under simulation
+     * @param <T> return value of the supplied function
+     * @return Value of supplied function
+     */
+    public <T> T simulateSeam(Supplier<T> f) {
+        MultipageCellLayout mcl = (MultipageCellLayout) mCellLayout;
+        if (mcl.isSeamWasAdded()) {
+            return f.get();
+        }
+        GridOccupancy auxGrid = mcl.getOccupied();
+        addSeam();
+        T res = f.get();
+        removeSeam();
+        mcl.setOccupied(auxGrid);
+        return res;
+    }
+
+    GridOccupancy createGridOccupancyWithSeam(GridOccupancy gridOccupancy) {
+        GridOccupancy grid = new GridOccupancy(mCellLayout.getCountX(), mCellLayout.getCountY());
+        for (int x = 0; x < mCellLayout.getCountX(); x++) {
+            for (int y = 0; y < mCellLayout.getCountY(); y++) {
+                int offset = x >= mCellLayout.getCountX() / 2 ? 1 : 0;
+                if (x == mCellLayout.getCountX() / 2) {
+                    grid.cells[x][y] = true;
+                } else {
+                    grid.cells[x][y] = gridOccupancy.cells[x - offset][y];
+                }
+            }
+        }
+        return grid;
+    }
+}
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
new file mode 100644
index 0000000..5e5eefe
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.celllayout;
+
+import android.view.View;
+
+import com.android.launcher3.CellLayout;
+
+/**
+ * Contains the logic of a reorder.
+ *
+ * The content of this class was extracted from {@link CellLayout} and should mimic the exact
+ * same behaviour.
+ */
+public class ReorderAlgorithm {
+
+    CellLayout mCellLayout;
+
+    public ReorderAlgorithm(CellLayout cellLayout) {
+        mCellLayout = cellLayout;
+    }
+
+    /**
+     * This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method
+     * will move items around and will change the shape of the item if possible to try to find a
+     * solution.
+     *
+     * When changing the size of the widget this method will try first subtracting -1 in the x
+     * dimension and then subtracting -1 in the y dimension until finding a possible solution or
+     * until it no longer can reduce the span.
+     *
+     * @param pixelX    X coordinate in pixels in the screen
+     * @param pixelY    Y coordinate in pixels in the screen
+     * @param minSpanX  minimum possible horizontal span it will try to find a solution for.
+     * @param minSpanY  minimum possible vertical span it will try to find a solution for.
+     * @param spanX     horizontal cell span
+     * @param spanY     vertical cell span
+     * @param direction direction in which it will try to push the items intersecting the desired
+     *                  view
+     * @param dragView  view being dragged in reorder
+     * @param decX      whether it will decrease the horizontal or vertical span if it can't find a
+     *                  solution for the current span.
+     * @param solution  variable to store the solution
+     * @return the same solution variable
+     */
+    public CellLayout.ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
+            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
+            CellLayout.ItemConfiguration solution) {
+        // Copy the current state into the solution. This solution will be manipulated as necessary.
+        mCellLayout.copyCurrentStateToSolution(solution, false);
+        // Copy the current occupied array into the temporary occupied array. This array will be
+        // manipulated as necessary to find a solution.
+        mCellLayout.getOccupied().copyTo(mCellLayout.mTmpOccupied);
+
+        // We find the nearest cell into which we would place the dragged item, assuming there's
+        // nothing in its way.
+        int[] result = new int[2];
+        result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY, result);
+
+        boolean success;
+        // First we try the exact nearest position of the item being dragged,
+        // we will then want to try to move this around to other neighbouring positions
+        success = mCellLayout.rearrangementExists(result[0], result[1], spanX, spanY, direction,
+                dragView, solution);
+
+        if (!success) {
+            // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
+            // x, then 1 in y etc.
+            if (spanX > minSpanX && (minSpanY == spanY || decX)) {
+                return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX - 1, spanY,
+                        direction, dragView, false, solution);
+            } else if (spanY > minSpanY) {
+                return findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY - 1,
+                        direction, dragView, true, solution);
+            }
+            solution.isSolution = false;
+        } else {
+            solution.isSolution = true;
+            solution.cellX = result[0];
+            solution.cellY = result[1];
+            solution.spanX = spanX;
+            solution.spanY = spanY;
+        }
+        return solution;
+    }
+
+    /**
+     * Returns a "reorder" if there is empty space without rearranging anything.
+     *
+     * @param pixelX   X coordinate in pixels in the screen
+     * @param pixelY   Y coordinate in pixels in the screen
+     * @param spanX    horizontal cell span
+     * @param spanY    vertical cell span
+     * @param dragView view being dragged in reorder
+     * @return the configuration that represents the found reorder
+     */
+    public CellLayout.ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
+            int spanY, View dragView) {
+        int[] result = new int[2];
+        if (mCellLayout.isNearestDropLocationOccupied(pixelX, pixelY, spanX, spanY, dragView,
+                result)) {
+            result[0] = result[1] = -1;
+        }
+        CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
+        mCellLayout.copyCurrentStateToSolution(solution, false);
+        solution.isSolution = result[0] != -1;
+        if (!solution.isSolution) {
+            return solution;
+        }
+        solution.cellX = result[0];
+        solution.cellY = result[1];
+        solution.spanX = spanX;
+        solution.spanY = spanY;
+        return solution;
+    }
+
+    /**
+     * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
+     * any other item in the way.
+     *
+     * @param pixelX X coordinate in pixels in the screen
+     * @param pixelY Y coordinate in pixels in the screen
+     * @param spanX  horizontal cell span
+     * @param spanY  vertical cell span
+     * @return the configuration that represents the found reorder
+     */
+    public CellLayout.ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
+            int minSpanX, int minSpanY, int spanX, int spanY) {
+        CellLayout.ItemConfiguration solution = new CellLayout.ItemConfiguration();
+        int[] result = new int[2];
+        int[] resultSpan = new int[2];
+        mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
+                resultSpan);
+        if (result[0] >= 0 && result[1] >= 0) {
+            mCellLayout.copyCurrentStateToSolution(solution, false);
+            solution.cellX = result[0];
+            solution.cellY = result[1];
+            solution.spanX = resultSpan[0];
+            solution.spanY = resultSpan[1];
+            solution.isSolution = true;
+        } else {
+            solution.isSolution = false;
+        }
+        return solution;
+    }
+
+    /**
+     * When the user drags an Item in the workspace sometimes we need to move the items already in
+     * the workspace to make space for the new item, this function return a solution for that
+     * reorder.
+     *
+     * @param pixelX   X coordinate in the screen of the dragView in pixels
+     * @param pixelY   Y coordinate in the screen of the dragView in pixels
+     * @param minSpanX minimum horizontal span the item can be shrunk to
+     * @param minSpanY minimum vertical span the item can be shrunk to
+     * @param spanX    occupied horizontal span
+     * @param spanY    occupied vertical span
+     * @param dragView the view of the item being draged
+     * @return returns a solution for the given parameters, the solution contains all the icons and
+     * the locations they should be in the given solution.
+     */
+    public CellLayout.ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX,
+            int minSpanY, int spanX, int spanY, View dragView) {
+        mCellLayout.getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView,
+                mCellLayout.mDirectionVector);
+
+        CellLayout.ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY,
+                spanX, spanY,
+                dragView);
+
+        // Find a solution involving pushing / displacing any items in the way
+        CellLayout.ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX,
+                minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true,
+                new CellLayout.ItemConfiguration());
+
+        // We attempt the approach which doesn't shuffle views at all
+        CellLayout.ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(
+                pixelX, pixelY, minSpanX, minSpanY, spanX, spanY);
+
+        // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
+        // favor a solution in which the item is not resized, but
+        if (swapSolution.isSolution && swapSolution.area() >= closestSpaceSolution.area()) {
+            return swapSolution;
+        } else if (closestSpaceSolution.isSolution) {
+            return closestSpaceSolution;
+        } else if (dropInPlaceSolution.isSolution) {
+            return dropInPlaceSolution;
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index e983a30..be995bc 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -126,8 +126,14 @@
         mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
                 view.getResources().getDisplayMetrics());
         mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
-        mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask();
-        mHideSysUiScrim = mTopScrim == null;
+        if (mTopScrim != null) {
+            mTopScrim.setDither(true);
+            mBottomMask = createDitheredAlphaMask();
+            mHideSysUiScrim = false;
+        } else {
+            mBottomMask = null;
+            mHideSysUiScrim = true;
+        }
 
         mDrawWallpaperScrim = FeatureFlags.ENABLE_WALLPAPER_SCRIM.get()
                 && !Themes.getAttrBoolean(view.getContext(), R.attr.isMainColorDark)
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index a0f21dc..be3a09b 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -60,9 +60,7 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 
 /**
  * A container for shortcuts to deep links and notifications associated with an app.
@@ -337,23 +335,6 @@
     }
 
     /**
-     * Shows the popup at the desired location, optionally reversing the children.
-     * @param viewsToFlip number of views from the top to to flip in case of reverse order
-     */
-    protected void reorderAndShow(int viewsToFlip) {
-        setupForDisplay();
-        boolean reverseOrder = !ENABLE_MATERIAL_U_POPUP.get() && mIsAboveIcon;
-        if (reverseOrder) {
-            reverseOrder(viewsToFlip);
-        }
-        assignMarginsAndBackgrounds(this);
-        if (shouldAddArrow()) {
-            addArrow();
-        }
-        animateOpen();
-    }
-
-    /**
      * Shows the popup at the desired location.
      */
     public void show() {
@@ -372,22 +353,6 @@
         orientAboutObject();
     }
 
-    private void reverseOrder(int viewsToFlip) {
-        int count = getChildCount();
-        ArrayList<View> allViews = new ArrayList<>(count);
-        for (int i = 0; i < count; i++) {
-            if (i == viewsToFlip) {
-                Collections.reverse(allViews);
-            }
-            allViews.add(getChildAt(i));
-        }
-        Collections.reverse(allViews);
-        removeAllViews();
-        for (int i = 0; i < count; i++) {
-            addView(allViews.get(i));
-        }
-    }
-
     private int getArrowLeft() {
         if (mIsLeftAligned) {
             return mArrowOffsetHorizontal;
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 8fef5c6..c20ac17 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -227,17 +227,18 @@
         if (ENABLE_MATERIAL_U_POPUP.get()) {
             container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                     R.layout.popup_container_material_u, launcher.getDragLayer(), false);
+            container.configureForLauncher(launcher);
             container.populateAndShowRowsMaterialU(icon, deepShortcutCount, systemShortcuts);
         } else {
             container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
                     R.layout.popup_container, launcher.getDragLayer(), false);
+            container.configureForLauncher(launcher);
             container.populateAndShow(
                     icon,
                     deepShortcutCount,
                     popupDataProvider.getNotificationKeysForItem(item),
                     systemShortcuts);
         }
-        container.configureForLauncher(launcher);
         launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
         container.requestFocus();
         return container;
@@ -257,14 +258,19 @@
         }
         // If there is only 1 shortcut, add it to its own container so it can show text and icon
         if (shortcuts.size() == 1) {
-            initializeSystemShortcut(R.layout.system_shortcut, this, shortcuts.get(0));
+            mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_rows_container,
+                    this, 0);
+            initializeSystemShortcut(R.layout.system_shortcut, mSystemShortcutContainer,
+                    shortcuts.get(0), false);
             return;
         }
-        mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons_container, this);
-        for (SystemShortcut shortcut : shortcuts) {
+        mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons_container, this, 0);
+        for (int i = 0; i < shortcuts.size(); i++) {
             initializeSystemShortcut(
-                    R.layout.system_shortcut_icon_only, mSystemShortcutContainer,
-                    shortcut);
+                    R.layout.system_shortcut_icon_only,
+                    mSystemShortcutContainer,
+                    shortcuts.get(i),
+                    i < shortcuts.size() - 1);
         }
     }
 
@@ -289,7 +295,6 @@
             }
             updateNotificationHeader();
         }
-        int viewsToFlip = getChildCount();
         mSystemShortcutContainer = this;
         if (mDeepShortcutContainer == null) {
             mDeepShortcutContainer = findViewById(R.id.deep_shortcuts_container);
@@ -314,8 +319,7 @@
             Optional<SystemShortcut.Widgets> widgetShortcutOpt = getWidgetShortcut(shortcuts);
             if (widgetShortcutOpt.isPresent()) {
                 if (mWidgetContainer == null) {
-                    mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container,
-                            this);
+                    mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container, this, 0);
                 }
                 initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
             }
@@ -324,14 +328,17 @@
         } else {
             mDeepShortcutContainer.setVisibility(View.GONE);
             if (!shortcuts.isEmpty()) {
-                for (SystemShortcut shortcut : shortcuts) {
-                    initializeSystemShortcut(R.layout.system_shortcut, this, shortcut);
+                for (int i = 0; i < shortcuts.size(); i++) {
+                    initializeSystemShortcut(
+                            R.layout.system_shortcut,
+                            this,
+                            shortcuts.get(i),
+                            i < shortcuts.size() - 1);
                 }
             }
         }
-
-        reorderAndShow(viewsToFlip);
-        showPopupContainer((ItemInfo) originalIcon.getTag(), notificationKeys);
+        show();
+        loadAppShortcuts((ItemInfo) originalIcon.getTag(), notificationKeys);
     }
 
     /**
@@ -351,19 +358,17 @@
             addAllShortcutsMaterialU(deepShortcutCount, systemShortcuts);
         } else if (!systemShortcuts.isEmpty()) {
             addSystemShortcutsMaterialU(systemShortcuts,
-                    R.layout.system_shortcut_rows_container_material_u,
+                    R.layout.system_shortcut_rows_container,
                     R.layout.system_shortcut);
         }
-
-        // no reversing needed for U design
-        reorderAndShow(0);
-        showPopupContainer((ItemInfo) originalIcon.getTag(), /* notificationKeys= */ emptyList());
+        show();
+        loadAppShortcuts((ItemInfo) originalIcon.getTag(), /* notificationKeys= */ emptyList());
     }
 
     /**
      * Animates and loads shortcuts on background thread for this popup container
      */
-    private void showPopupContainer(ItemInfo originalItemInfo,
+    private void loadAppShortcuts(ItemInfo originalItemInfo,
             List<NotificationKeyData> notificationKeys) {
 
         if (ATLEAST_P) {
@@ -390,7 +395,7 @@
         if (deepShortcutCount + systemShortcuts.size() <= SHORTCUT_COLLAPSE_THRESHOLD) {
             // add all system shortcuts including widgets shortcut to same container
             addSystemShortcutsMaterialU(systemShortcuts,
-                    R.layout.system_shortcut_rows_container_material_u,
+                    R.layout.system_shortcut_rows_container,
                     R.layout.system_shortcut);
             addDeepShortcutsMaterialU(deepShortcutCount);
             return;
@@ -458,8 +463,12 @@
             return;
         }
         mSystemShortcutContainer = inflateAndAdd(systemShortcutContainerLayout, this);
-        for (SystemShortcut shortcut : systemShortcuts) {
-            initializeSystemShortcut(systemShortcutLayout, mSystemShortcutContainer, shortcut);
+        for (int i = 0; i < systemShortcuts.size(); i++) {
+            initializeSystemShortcut(
+                    systemShortcutLayout,
+                    mSystemShortcutContainer,
+                    systemShortcuts.get(i),
+                    i < systemShortcuts.size() - 1);
         }
     }
 
@@ -533,20 +542,31 @@
     }
 
     protected void initializeWidgetShortcut(ViewGroup container, SystemShortcut info) {
-        View view = initializeSystemShortcut(R.layout.system_shortcut, container, info);
+        View view = initializeSystemShortcut(R.layout.system_shortcut, container, info, false);
         view.getLayoutParams().width = mContainerWidth;
     }
 
-    protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) {
-        View view = inflateAndAdd(
-                resId, container, getInsertIndexForSystemShortcut(container, info));
+    /**
+     * Initializes and adds View for given SystemShortcut to a container.
+     * @param resId Resource id to use for SystemShortcut View.
+     * @param container ViewGroup to add the shortcut View to as a parent
+     * @param info The SystemShortcut instance to create a View for.
+     * @param shouldAddSpacer If True, will add a spacer after the shortcut, when showing the
+     *                        SystemShortcut as an icon only. Used to space the shortcut icons
+     *                        evenly.
+     * @return The view inflated for the SystemShortcut
+     */
+    protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info,
+            boolean shouldAddSpacer) {
+        View view = inflateAndAdd(resId, container);
         if (view instanceof DeepShortcutView) {
-            // Expanded system shortcut, with both icon and text shown on white background.
+            // System shortcut takes entire row with icon and text
             final DeepShortcutView shortcutView = (DeepShortcutView) view;
             info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
         } else if (view instanceof ImageView) {
-            // Only the system shortcut icon shows on a gray background header.
+            // System shortcut is just an icon
             info.setIconAndContentDescriptionFor((ImageView) view);
+            if (shouldAddSpacer) inflateAndAdd(R.layout.system_shortcut_spacer, container);
             view.setTooltipText(view.getContentDescription());
         }
         view.setTag(info);
@@ -555,17 +575,6 @@
     }
 
     /**
-     * Returns an index for inserting a shortcut into a container.
-     */
-    private int getInsertIndexForSystemShortcut(ViewGroup container, SystemShortcut shortcut) {
-        final View separator = container.findViewById(R.id.separator);
-
-        return separator != null && shortcut.isLeftGroup() ?
-                container.indexOfChild(separator) :
-                container.getChildCount();
-    }
-
-    /**
      * Determines when the deferred drag should be started.
      *
      * Current behavior:
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 68ece03..4f94c92 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -33,6 +33,7 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.Nullable;
+import androidx.annotation.Px;
 import androidx.core.view.ViewCompat;
 
 import com.android.launcher3.DeviceProfile;
@@ -61,7 +62,7 @@
         implements OnClickListener, OnLongClickListener, DragSource,
         PopupDataProvider.PopupDataChangeListener, Insettable {
     /** The default number of cells that can fit horizontally in a widget sheet. */
-    protected static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
+    public static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
 
     protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
             "launcher.widgets_education_tip_seen";
@@ -70,15 +71,18 @@
     /* Touch handling related member variables. */
     private Toast mWidgetInstructionToast;
 
-    private int mContentHorizontalMarginInPx;
+    @Px protected int mContentHorizontalMargin;
+    @Px protected int mWidgetCellHorizontalPadding;
 
     protected int mNavBarScrimHeight;
     private final Paint mNavBarScrimPaint;
 
     public BaseWidgetSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mContentHorizontalMarginInPx = getResources().getDimensionPixelSize(
+        mContentHorizontalMargin = getResources().getDimensionPixelSize(
                 R.dimen.widget_list_horizontal_margin);
+        mWidgetCellHorizontalPadding = getResources().getDimensionPixelSize(
+                R.dimen.widget_cell_horizontal_padding);
         mNavBarScrimPaint = new Paint();
         mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
     }
@@ -138,11 +142,11 @@
     @Override
     public void setInsets(Rect insets) {
         mInsets.set(insets);
-        int contentHorizontalMarginInPx = getResources().getDimensionPixelSize(
+        @Px int contentHorizontalMargin = getResources().getDimensionPixelSize(
                 R.dimen.widget_list_horizontal_margin);
-        if (contentHorizontalMarginInPx != mContentHorizontalMarginInPx) {
-            onContentHorizontalMarginChanged(contentHorizontalMarginInPx);
-            mContentHorizontalMarginInPx = contentHorizontalMarginInPx;
+        if (contentHorizontalMargin != mContentHorizontalMargin) {
+            onContentHorizontalMarginChanged(contentHorizontalMargin);
+            mContentHorizontalMargin = contentHorizontalMargin;
         }
     }
 
@@ -198,19 +202,6 @@
                 MeasureSpec.getSize(heightMeasureSpec));
     }
 
-    /** Returns the number of cells that can fit horizontally in a given {@code content}. */
-    protected int computeMaxHorizontalSpans(View content, int contentHorizontalPaddingPx) {
-        DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
-        int availableWidth = content.getMeasuredWidth()
-                - contentHorizontalPaddingPx
-                - (2 * mContentHorizontalMarginInPx);
-        Point cellSize = deviceProfile.getCellSize();
-        if (cellSize.x > 0) {
-            return availableWidth / cellSize.x;
-        }
-        return DEFAULT_MAX_HORIZONTAL_SPANS;
-    }
-
     private boolean beginDraggingWidget(WidgetCell v) {
         if (TestProtocol.sDebugTracing) {
             Log.d(TestProtocol.NO_DROP_TARGET, "2");
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 4099302..06c622d 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -36,6 +36,8 @@
 import android.widget.TableRow;
 import android.widget.TextView;
 
+import androidx.annotation.Px;
+
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.WidgetItem;
@@ -69,8 +71,7 @@
     private static final long EDUCATION_TIP_DELAY_MS = 300;
 
     private ItemInfo mOriginalItemInfo;
-    private int mMaxHorizontalSpan = DEFAULT_MAX_HORIZONTAL_SPANS;
-    private final int mWidgetCellHorizontalPadding;
+    @Px private int mMaxHorizontalSpan;
 
     private final OnLayoutChangeListener mLayoutChangeListenerToShowTips =
             new OnLayoutChangeListener() {
@@ -111,8 +112,6 @@
         if (!hasSeenEducationTip()) {
             addOnLayoutChangeListener(mLayoutChangeListenerToShowTips);
         }
-        mWidgetCellHorizontalPadding = getResources().getDimensionPixelSize(
-                R.dimen.widget_cell_horizontal_padding);
         setContentBackground(getContext().getDrawable(R.drawable.bg_rounded_corner_bottom_sheet));
     }
 
@@ -134,7 +133,7 @@
     private boolean updateMaxSpansPerRow() {
         if (getMeasuredWidth() == 0) return false;
 
-        int maxHorizontalSpan = computeMaxHorizontalSpans(mContent, mWidgetCellHorizontalPadding);
+        @Px int maxHorizontalSpan = mContent.getMeasuredWidth() - (2 * mContentHorizontalMargin);
         if (mMaxHorizontalSpan != maxHorizontalSpan) {
             // Ensure the table layout is showing widgets in the right column after measure.
             mMaxHorizontalSpan = maxHorizontalSpan;
@@ -184,7 +183,9 @@
         TableLayout widgetsTable = findViewById(R.id.widgets_table);
         widgetsTable.removeAllViews();
 
-        WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering(widgets, mMaxHorizontalSpan)
+        WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgets, mActivityContext,
+                mActivityContext.getDeviceProfile(), mMaxHorizontalSpan,
+                mWidgetCellHorizontalPadding)
                 .forEach(row -> {
                     TableRow tableRow = new TableRow(getContext());
                     tableRow.setGravity(Gravity.TOP);
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
index 626e0b9..d709196 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.widget.model;
 
+import androidx.annotation.Px;
+
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 
@@ -26,7 +28,7 @@
  */
 public final class WidgetsListContentEntry extends WidgetsListBaseEntry {
 
-    private final int mMaxSpanSizeInCells;
+    @Px private final int mMaxSpanSize;
 
     /**
      * Constructor for {@link WidgetsListContentEntry}.
@@ -37,7 +39,7 @@
      */
     public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName,
             List<WidgetItem> items) {
-        this(pkgItem, titleSectionName, items, /* maxSpanSizeInCells= */ 0);
+        this(pkgItem, titleSectionName, items, /* maxSpanSize= */ 0);
     }
 
     /**
@@ -46,43 +48,43 @@
      * @param pkgItem package info associated with the entry
      * @param titleSectionName title section name associated with the entry.
      * @param items list of widgets for the package.
-     * @param maxSpanSizeInCells the max horizontal span in cells that is allowed for grouping more
+     * @param maxSpanSize the max horizontal span in pixels that is allowed for grouping more
      *                           than one widgets in a table row.
      */
     public WidgetsListContentEntry(PackageItemInfo pkgItem, String titleSectionName,
-            List<WidgetItem> items, int maxSpanSizeInCells) {
+            List<WidgetItem> items, @Px int maxSpanSize) {
         super(pkgItem, titleSectionName, items);
-        mMaxSpanSizeInCells = maxSpanSizeInCells;
+        mMaxSpanSize = maxSpanSize;
     }
 
     @Override
     public String toString() {
-        return "Content:" + mPkgItem.packageName + ":" + mWidgets.size() + " maxSpanSizeInCells: "
-                + mMaxSpanSizeInCells;
+        return "Content:" + mPkgItem.packageName + ":" + mWidgets.size() + " maxSpanSize: "
+                + mMaxSpanSize;
     }
 
     /**
-     * Returns a copy of this {@link WidgetsListContentEntry} with updated
-     * {@param maxSpanSizeInCells}.
+     * Returns a copy of this {@link WidgetsListContentEntry} with updated {@code maxSpanSize}.
      *
-     * @param maxSpanSizeInCells the maximum horizontal span in cells that is allowed for grouping
+     * @param maxSpanSize the maximum horizontal span in pixels that is allowed for grouping
      *                           more than one widgets in a table row.
      */
-    public WidgetsListContentEntry withMaxSpanSize(int maxSpanSizeInCells) {
-        if (mMaxSpanSizeInCells == maxSpanSizeInCells) return this;
+    public WidgetsListContentEntry withMaxSpanSize(@Px int maxSpanSize) {
+        if (mMaxSpanSize == maxSpanSize) return this;
         return new WidgetsListContentEntry(
                 mPkgItem,
                 mTitleSectionName,
                 mWidgets,
-                /* maxSpanSizeInCells= */ maxSpanSizeInCells);
+                /* maxSpanSize= */ maxSpanSize);
     }
 
     /**
-     * Returns the max horizontal span size in cells that is allowed for grouping more than one
+     * Returns the max horizontal span size in pixels that is allowed for grouping more than one
      * widget in a table row.
      */
-    public int getMaxSpanSizeInCells() {
-        return mMaxSpanSizeInCells;
+    @Px
+    public int getMaxSpanSize() {
+        return mMaxSpanSize;
     }
 
     @Override
@@ -91,6 +93,6 @@
         WidgetsListContentEntry otherEntry = (WidgetsListContentEntry) obj;
         return mWidgets.equals(otherEntry.mWidgets) && mPkgItem.equals(otherEntry.mPkgItem)
                 && mTitleSectionName.equals(otherEntry.mTitleSectionName)
-                && mMaxSpanSizeInCells == otherEntry.mMaxSpanSizeInCells;
+                && mMaxSpanSize == otherEntry.mMaxSpanSize;
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index f4d6749..d5c4315 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -53,6 +53,7 @@
 import androidx.annotation.FloatRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.Px;
 import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.RecyclerView;
@@ -181,14 +182,13 @@
         }
     };
 
-    private final int mTabsHeight;
-    private final int mWidgetSheetContentHorizontalPadding;
+    @Px private final int mTabsHeight;
 
     @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView;
     @Nullable private PersonalWorkPagedView mViewPager;
     private boolean mIsInSearchMode;
     private boolean mIsNoWidgetsViewNeeded;
-    private int mMaxSpansPerRow = DEFAULT_MAX_HORIZONTAL_SPANS;
+    @Px private int mMaxSpanPerRow;
     private TextView mNoWidgetsView;
 
     private StickyHeaderLayout mSearchScrollView;
@@ -224,8 +224,6 @@
         mTabsHeight = mHasWorkProfile
                 ? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
                 : 0;
-        mWidgetSheetContentHorizontalPadding = 2 * resources.getDimensionPixelSize(
-                R.dimen.widget_cell_horizontal_padding);
 
         mUserManagerState.init(UserCache.INSTANCE.get(context),
                 context.getSystemService(UserManager.class));
@@ -337,7 +335,7 @@
                 : mSearchScrollView.findViewById(R.id.title);
         mRightPane = mIsTwoPane ? mContent.findViewById(R.id.right_pane) : null;
         mWidgetsListTableViewHolderBinder =
-                new WidgetsListTableViewHolderBinder(layoutInflater, this, this);
+                new WidgetsListTableViewHolderBinder(mActivityContext, layoutInflater, this, this);
         onRecommendedWidgetsBound();
         onWidgetsBound();
 
@@ -536,22 +534,20 @@
         View content = mHasWorkProfile
                 ? mViewPager
                 : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
-
         if (mIsTwoPane && mRightPane != null) {
             content = mRightPane;
         }
 
-        int maxHorizontalSpans = computeMaxHorizontalSpans(content,
-                mWidgetSheetContentHorizontalPadding);
-        if (mMaxSpansPerRow != maxHorizontalSpans) {
-            mMaxSpansPerRow = maxHorizontalSpans;
-            mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
-                    mMaxSpansPerRow);
-            mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
-                    mMaxSpansPerRow);
+        @Px int maxHorizontalSpan = content.getMeasuredWidth() - (2 * mContentHorizontalMargin);
+        if (mMaxSpanPerRow != maxHorizontalSpan) {
+            mMaxSpanPerRow = maxHorizontalSpan;
+            mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
+                    maxHorizontalSpan);
+            mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
+                    maxHorizontalSpan);
             if (mHasWorkProfile) {
-                mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPerRow(
-                        mMaxSpansPerRow);
+                mAdapters.get(AdapterHolder.WORK).mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(
+                        maxHorizontalSpan);
             }
             onRecommendedWidgetsBound();
             return true;
@@ -700,8 +696,12 @@
                     - noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
 
             List<ArrayList<WidgetItem>> recommendedWidgetsInTable =
-                    WidgetsTableUtils.groupWidgetItemsIntoTableWithoutReordering(
-                            recommendedWidgets, mMaxSpansPerRow);
+                    WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(
+                            recommendedWidgets,
+                            mActivityContext,
+                            mActivityContext.getDeviceProfile(),
+                            mMaxSpanPerRow,
+                            mWidgetCellHorizontalPadding);
             mRecommendedWidgetsTable.setRecommendedWidgets(
                     recommendedWidgetsInTable, maxTableHeight);
         } else {
@@ -1051,7 +1051,7 @@
             if (mAdapterType == PRIMARY || mAdapterType == WORK) {
                 mWidgetsRecyclerView.addOnAttachStateChangeListener(mBindScrollbarInSearchMode);
             }
-            mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
+            mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow);
         }
     }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
index c28402e..c89eea8 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_DEFAULT;
 import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_FIRST;
 import static com.android.launcher3.recyclerview.ViewHolderBinder.POSITION_LAST;
+import static com.android.launcher3.widget.BaseWidgetSheet.DEFAULT_MAX_HORIZONTAL_SPANS;
 
 import android.content.Context;
 import android.os.Process;
@@ -32,6 +33,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.Px;
 import androidx.recyclerview.widget.DiffUtil;
 import androidx.recyclerview.widget.DiffUtil.DiffResult;
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -49,6 +51,7 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.util.WidgetSizes;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -99,7 +102,7 @@
     @Nullable private Predicate<WidgetsListBaseEntry> mFilter = null;
     @Nullable private RecyclerView mRecyclerView;
     @Nullable private PackageUserKey mPendingClickHeader;
-    private int mMaxSpanSize = 4;
+    @Px private int mMaxHorizontalSpan;
 
     public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
             IntSupplier emptySpaceHeightProvider, OnClickListener iconClickListener,
@@ -107,11 +110,14 @@
             WidgetsFullSheet.HeaderChangeListener headerChangeListener) {
         mHeaderChangeListener = headerChangeListener;
         mContext = context;
+        mMaxHorizontalSpan = WidgetSizes.getWidgetSizePx(
+                ActivityContext.lookupContext(context).getDeviceProfile(),
+                        DEFAULT_MAX_HORIZONTAL_SPANS, 1).getWidth();
 
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_LIST,
                 new WidgetsListTableViewHolderBinder(
-                        layoutInflater, iconClickListener, iconLongClickListener));
+                        mContext, layoutInflater, iconClickListener, iconLongClickListener));
         mViewHolderBinders.put(
                 VIEW_TYPE_WIDGETS_HEADER,
                 new WidgetsListHeaderViewHolderBinder(
@@ -199,7 +205,8 @@
                     } else if (entry instanceof WidgetsListContentEntry) {
                         // Adjust the original content entries to accommodate for the current
                         // maxSpanSize.
-                        return ((WidgetsListContentEntry) entry).withMaxSpanSize(mMaxSpanSize);
+                        return ((WidgetsListContentEntry) entry).withMaxSpanSize(
+                                mMaxHorizontalSpan);
                     }
                     return entry;
                 })
@@ -407,11 +414,11 @@
     }
 
     /**
-     * Sets the max horizontal span in cells that is allowed for grouping more than one widget in a
+     * Sets the max horizontal span in pixels that is allowed for grouping more than one widget in a
      * table row.
      */
-    public void setMaxHorizontalSpansPerRow(int maxHorizontalSpans) {
-        mMaxSpanSize = maxHorizontalSpans;
+    public void setMaxHorizontalSpansPxPerRow(@Px int maxHorizontalSpan) {
+        mMaxHorizontalSpan = maxHorizontalSpan;
         updateVisibleEntries();
     }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
index 2e8f0ab..c7d2aa3 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinder.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.widget.picker;
 
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.util.Log;
 import android.util.Pair;
@@ -27,9 +28,13 @@
 import android.widget.TableLayout;
 import android.widget.TableRow;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+
 import com.android.launcher3.R;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
@@ -47,13 +52,21 @@
 
     private final LayoutInflater mLayoutInflater;
     private final OnClickListener mIconClickListener;
+    private @NonNull final Context mContext;
+    private @NonNull final ActivityContext mActivityContext;
+    @Px private final int mCellPadding;
     private final OnLongClickListener mIconLongClickListener;
 
     public WidgetsListTableViewHolderBinder(
+            @NonNull Context context,
             LayoutInflater layoutInflater,
             OnClickListener iconClickListener,
             OnLongClickListener iconLongClickListener) {
         mLayoutInflater = layoutInflater;
+        mContext = context;
+        mActivityContext = ActivityContext.lookupContext(context);
+        mCellPadding = context.getResources().getDimensionPixelSize(
+                R.dimen.widget_cell_horizontal_padding);
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
     }
@@ -87,8 +100,11 @@
                         (position & POSITION_LAST) != 0));
 
         List<ArrayList<WidgetItem>> widgetItemsTable =
-                WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering(
-                        entry.mWidgets, entry.getMaxSpanSizeInCells());
+                WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(entry.mWidgets,
+                        mContext,
+                        mActivityContext.getDeviceProfile(),
+                        entry.getMaxSpanSize(),
+                        mCellPadding);
         recycleTableBeforeBinding(table, widgetItemsTable);
 
         // Bind the widget items.
diff --git a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
index 72e27bf..74d3062 100644
--- a/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
+++ b/src/com/android/launcher3/widget/util/WidgetsTableUtils.java
@@ -15,6 +15,11 @@
  */
 package com.android.launcher3.widget.util;
 
+import android.content.Context;
+
+import androidx.annotation.Px;
+
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.model.WidgetItem;
 
 import java.util.ArrayList;
@@ -49,34 +54,41 @@
      * Groups {@code widgetItems} items into a 2D array which matches their appearance in a UI
      * table. This takes liberty to rearrange widgets to make the table visually appealing.
      */
-    public static List<ArrayList<WidgetItem>> groupWidgetItemsIntoTableWithReordering(
-            List<WidgetItem> widgetItems, final int maxSpansPerRow) {
+    public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithReordering(
+            List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
+            final @Px int rowPx, final @Px int cellPadding) {
         List<WidgetItem> sortedWidgetItems = widgetItems.stream().sorted(WIDGET_SHORTCUT_COMPARATOR)
                 .collect(Collectors.toList());
-        return groupWidgetItemsIntoTableWithoutReordering(sortedWidgetItems, maxSpansPerRow);
+        return groupWidgetItemsUsingRowPxWithoutReordering(sortedWidgetItems, context, dp, rowPx,
+                cellPadding);
     }
 
     /**
      * Groups {@code widgetItems} into a 2D array which matches their appearance in a UI table while
-     * maintaining their order.
+     * maintaining their order. This function is a variant of
+     * {@code groupWidgetItemsIntoTableWithoutReordering} in that this uses widget pixels for
+     * calculation.
      *
      * <p>Grouping:
      * 1. Widgets and shortcuts never group together in the same row.
-     * 2. The ordered widgets are grouped together in the same row until their total horizontal
-     *    spans exceed the {@code maxSpansPerRow} - 1.
-     * 3. The order shortcuts are grouped together in the same row until their total horizontal
-     *    spans exceed the {@code maxSpansPerRow} - 1.
-     * 4. If there is only one widget in a row, its width may exceed the {@code maxSpansPerRow}.
+     * 2. The ordered widgets are grouped together in the same row until their individual occupying
+     *    pixels exceed the total allowed pixels for the cell.
+     * 3. The ordered shortcuts are grouped together in the same row until their individual
+     *    occupying pixels exceed the total allowed pixels for the cell.
+     * 4. If there is only one widget in a row, its width may exceed the {@code rowPx}.
      *
-     * <p>Let's say the {@code maxSpansPerRow} is set to 6. Widgets can be grouped in the same row
-     * if their total horizontal spans added don't exceed 5.
-     * Example 1: Row 1: 2x2, 2x3, 1x1. Total horizontal spans is 5. This is okay.
-     * Example 2: Row 1: 2x2, 4x3, 1x1. the total horizontal spans is 7. This is wrong. 4x3 and 1x1
-     * should be moved to a new row.
-     * Example 3: Row 1: 6x4. This is okay because this is the only item in the row.
+     * <p>Let's say the {@code rowPx} is set to 600 and we have 5 widgets. Widgets can be grouped
+     * in the same row if each of their individual occupying pixels does not exceed
+     * {@code rowPx} / 5 - 2 * {@code cellPadding}.
+     * Example 1: Row 1: 200x200, 200x300, 100x100. Average horizontal pixels is 200 and no widgets
+     * exceed that width. This is okay.
+     * Example 2: Row 1: 200x200, 400x300, 100x100. Average horizontal pixels is 200 and one widget
+     * exceed that width. This is not allowed.
+     * Example 3: Row 1: 700x400. This is okay because this is the only item in the row.
      */
-    public static List<ArrayList<WidgetItem>> groupWidgetItemsIntoTableWithoutReordering(
-            List<WidgetItem> widgetItems, final int maxSpansPerRow) {
+    public static List<ArrayList<WidgetItem>> groupWidgetItemsUsingRowPxWithoutReordering(
+            List<WidgetItem> widgetItems, Context context, final DeviceProfile dp,
+            final @Px int rowPx, final @Px int cellPadding) {
 
         List<ArrayList<WidgetItem>> widgetItemsTable = new ArrayList<>();
         ArrayList<WidgetItem> widgetItemsAtRow = null;
@@ -86,23 +98,28 @@
                 widgetItemsTable.add(widgetItemsAtRow);
             }
             int numOfWidgetItems = widgetItemsAtRow.size();
-            int totalHorizontalSpan = widgetItemsAtRow.stream().map(item -> item.spanX)
-                    .reduce(/* default= */ 0, Integer::sum);
-            int totalHorizontalSpanAfterAddingWidget = widgetItem.spanX + totalHorizontalSpan;
+            @Px int individualSpan = (rowPx / (numOfWidgetItems + 1)) - (2 * cellPadding);
             if (numOfWidgetItems == 0) {
                 widgetItemsAtRow.add(widgetItem);
             } else if (
-                    // The max spans per row is reduced by 1 to ensure we don't pack too many
-                    // 1xn widgets on the same row, which may reduce the space for rendering a
-                    // widget's description.
-                    totalHorizontalSpanAfterAddingWidget <= maxSpansPerRow - 1
-                            && widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))) {
+                    // Since the size of the widget cell is determined by dividing the maximum span
+                    // pixels evenly, making sure that each widget would have enough span pixels to
+                    // show their contents.
+                    widgetItem.hasSameType(widgetItemsAtRow.get(numOfWidgetItems - 1))
+                    && widgetItemsAtRow.stream().allMatch(
+                            item -> WidgetSizes.getWidgetItemSizePx(context, dp, item)
+                                    .getWidth() <= individualSpan)
+                    && WidgetSizes.getWidgetItemSizePx(context, dp, widgetItem)
+                            .getWidth() <= individualSpan) {
                 // Group items in the same row if
                 // 1. they are with the same type, i.e. a row can only have widgets or shortcuts but
                 //    never a mix of both.
-                // 2. the total number of horizontal spans are smaller than or equal to
-                //    MAX_SPAN_PER_ROW. If an item has a horizontal span > MAX_SPAN_PER_ROW, we just
-                //    place it in its own row regardless of the horizontal span limit.
+                // 2. Each widget will have horizontal cell span pixels that is at least as large as
+                //    it is required to fit in the horizontal content, unless the widget horizontal
+                //    span pixels is larger than the maximum allowed.
+                //    If an item has horizontal span pixels larger than the maximum allowed pixels
+                //    per row, we just place it in its own row regardless of the horizontal span
+                //    limit.
                 widgetItemsAtRow.add(widgetItem);
             } else {
                 widgetItemsAtRow = new ArrayList<>();
diff --git a/tests/Android.bp b/tests/Android.bp
index dfbaf86..81853d1 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -116,7 +116,8 @@
     manifest: "AndroidManifest.xml",
     platform_apis: true,
     test_config: "Launcher3Tests.xml",
-    data: [":Launcher3"]
+    data: [":Launcher3"],
+    test_suites: ["general-tests"],
 }
 
 // Shared between tests and launcher
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 19b8b0c..5f516eb 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -29,7 +29,9 @@
 
 import android.content.Intent;
 import android.graphics.Point;
+import android.os.SystemClock;
 import android.platform.test.annotations.IwTest;
+import android.util.Log;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -525,9 +527,11 @@
     @Test
     @PortraitLandscape
     public void testDragAppIconToWorkspaceCell() throws Exception {
+        long startTime, endTime, elapsedTime;
         Point[] targets = getCornersAndCenterPositions();
 
         for (Point target : targets) {
+            startTime = SystemClock.uptimeMillis();
             final HomeAllApps allApps = mLauncher.getWorkspace().switchToAllApps();
             allApps.freeze();
             try {
@@ -537,12 +541,21 @@
             }
             // Reset the workspace for the next shortcut creation.
             initialize(this);
+            endTime = SystemClock.uptimeMillis();
+            elapsedTime = endTime - startTime;
+            Log.d("testDragAppIconToWorkspaceCellTime",
+                    "Milliseconds taken to drag app icon to workspace cell: " + elapsedTime);
         }
 
         // test to move a shortcut to other cell.
         final HomeAppIcon launcherTestAppIcon = createShortcutInCenterIfNotExist(APP_NAME);
         for (Point target : targets) {
+            startTime = SystemClock.uptimeMillis();
             launcherTestAppIcon.dragToWorkspace(target.x, target.y);
+            endTime = SystemClock.uptimeMillis();
+            elapsedTime = endTime - startTime;
+            Log.d("testDragAppIconToWorkspaceCellTime",
+                    "Milliseconds taken to move shortcut to other cell: " + elapsedTime);
         }
     }
 
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 9669010..bf9eb5a 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.testcomponent.AppWidgetWithConfig;
 import com.android.launcher3.testcomponent.RequestPinItemActivity;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.Wait.Condition;
@@ -75,6 +76,7 @@
         super.setUp();
         mCallbackAction = UUID.randomUUID().toString();
         mShortcutId = UUID.randomUUID().toString();
+        TaplTestsLauncher3.initialize(this);
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 9dc46f1..e0101f5 100644
--- a/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -94,6 +94,7 @@
         }).when(mIconCache).getTitleNoCache(any());
 
         mViewHolderBinder = new WidgetsListTableViewHolderBinder(
+                mContext,
                 LayoutInflater.from(mContext),
                 mOnIconClickListener,
                 mOnLongClickListener);
diff --git a/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
index 715dcca..d2c2fd7 100644
--- a/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
@@ -23,6 +23,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
 
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -35,11 +36,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.icons.ComponentWithLabel;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
+import com.android.launcher3.util.ActivityContextWrapper;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
 
@@ -57,11 +60,19 @@
 public final class WidgetsTableUtilsTest {
     private static final String TEST_PACKAGE = "com.google.test";
 
+    private static final int SPACE_SIZE = 10;
+    private static final int CELL_SIZE = 50;
+    private static final int NUM_OF_COLS = 5;
+    private static final int NUM_OF_ROWS = 5;
+
     @Mock
     private IconCache mIconCache;
 
+    @Mock
+    private DeviceProfile mTestDeviceProfile;
+
     private Context mContext;
-    private InvariantDeviceProfile mTestProfile;
+    private InvariantDeviceProfile mTestInvariantProfile;
     private WidgetItem mWidget1x1;
     private WidgetItem mWidget2x2;
     private WidgetItem mWidget2x3;
@@ -76,12 +87,13 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mContext = getApplicationContext();
+        mContext = new ActivityContextWrapper(getApplicationContext());
 
-        mTestProfile = new InvariantDeviceProfile();
-        mTestProfile.numRows = 5;
-        mTestProfile.numColumns = 5;
+        mTestInvariantProfile = new InvariantDeviceProfile();
+        mTestInvariantProfile.numColumns = NUM_OF_COLS;
+        mTestInvariantProfile.numRows = NUM_OF_ROWS;
 
+        initDP();
         initTestWidgets();
         initTestShortcuts();
 
@@ -92,17 +104,17 @@
 
 
     @Test
-    public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpansPerRow5_shouldGroupWidgetsInTable() {
+    public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding0_shouldGroupWidgetsInTable() {
         List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
                 mWidget2x2);
 
         List<ArrayList<WidgetItem>> widgetItemInTable =
-                WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering(
-                        widgetItems, /* maxSpansPerRow= */ 5);
+                WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
+                        mTestDeviceProfile, 220, 0);
 
-        // Row 0: 1x1, 2x2
-        // Row 1: 2x3, 2x4
-        // Row 2: 4x4
+        // Row 0: 1x1(50px), 2x2(110px)
+        // Row 1: 2x3(110px), 2x4(110px)
+        // Row 2: 4x4(230px)
         assertThat(widgetItemInTable).hasSize(3);
         assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
         assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget2x4);
@@ -110,65 +122,91 @@
     }
 
     @Test
-    public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpansPerRow4_shouldGroupWidgetsInTable() {
+    public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow220_cellPadding10_shouldGroupWidgetsInTable() {
         List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
                 mWidget2x2);
 
         List<ArrayList<WidgetItem>> widgetItemInTable =
-                WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering(
-                        widgetItems, /* maxSpansPerRow= */ 4);
+                WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
+                        mTestDeviceProfile, 220, 10);
 
-        // Row 0: 1x1, 2x2
-        // Row 1: 2x3,
-        // Row 2: 2x4,
-        // Row 3: 4x4
-        assertThat(widgetItemInTable).hasSize(4);
-        assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
-        assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3);
-        assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4);
-        assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
+        // Row 0: 1x1(50px), 2x2(110px)
+        // Row 1: 2x3(110px), 2x4(110px)
+        // Row 2: 4x4(230px)
+        assertThat(widgetItemInTable).hasSize(5);
+        assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1);
+        assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x2);
+        assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x3);
+        assertThat(widgetItemInTable.get(3)).containsExactly(mWidget2x4);
+        assertThat(widgetItemInTable.get(4)).containsExactly(mWidget4x4);
     }
 
     @Test
-    public void groupWidgetItemsIntoTableWithReordering_mixItems_maxSpansPerRow4_shouldGroupWidgetsInTable() {
+    public void groupWidgetItemsIntoTableWithReordering_widgetsOnly_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() {
+        List<WidgetItem> widgetItems = List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4,
+                mWidget2x2);
+
+        List<ArrayList<WidgetItem>> widgetItemInTable =
+                WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
+                        mTestDeviceProfile, 350, 0);
+
+        // Row 0: 1x1(50px), 2x2(110px), 2x3(110px)
+        // Row 1: 2x4(110px)
+        // Row 2: 4x4(230px)
+        assertThat(widgetItemInTable).hasSize(3);
+        assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3);
+        assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4);
+        assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+    }
+
+    @Test
+    public void groupWidgetItemsIntoTableWithReordering_mixItems_maxSpanPxPerRow350_cellPadding0_shouldGroupWidgetsInTable() {
         List<WidgetItem> widgetItems = List.of(mWidget4x4, mShortcut3, mWidget2x3, mShortcut1,
                 mWidget1x1, mShortcut2, mWidget2x4, mWidget2x2);
 
         List<ArrayList<WidgetItem>> widgetItemInTable =
-                WidgetsTableUtils.groupWidgetItemsIntoTableWithReordering(
-                        widgetItems, /* maxSpansPerRow= */ 4);
+                WidgetsTableUtils.groupWidgetItemsUsingRowPxWithReordering(widgetItems, mContext,
+                        mTestDeviceProfile, 350, 0);
 
-        // Row 0: 1x1, 2x2
-        // Row 1: 2x3,
-        // Row 2: 2x4,
-        // Row 3: 4x4
-        // Row 4: shortcut3, shortcut1, shortcut2
-        assertThat(widgetItemInTable).hasSize(5);
-        assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2);
-        assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3);
-        assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4);
-        assertThat(widgetItemInTable.get(3)).containsExactly(mWidget4x4);
-        assertThat(widgetItemInTable.get(4)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
+        // Row 0: 1x1(50px), 2x2(110px), 2x3(110px)
+        // Row 1: 2x4(110px),
+        // Row 2: 4x4(230px)
+        // Row 3: shortcut3(50px), shortcut1(50px), shortcut2(50px)
+        assertThat(widgetItemInTable).hasSize(4);
+        assertThat(widgetItemInTable.get(0)).containsExactly(mWidget1x1, mWidget2x2, mWidget2x3);
+        assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x4);
+        assertThat(widgetItemInTable.get(2)).containsExactly(mWidget4x4);
+        assertThat(widgetItemInTable.get(3)).containsExactly(mShortcut3, mShortcut2, mShortcut1);
     }
 
     @Test
-    public void groupWidgetItemsIntoTableWithoutReordering_shouldMaintainTheOrder() {
+    public void groupWidgetItemsIntoTableWithoutReordering_maxSpanPxPerRow220_cellPadding0_shouldMaintainTheOrder() {
         List<WidgetItem> widgetItems =
                 List.of(mWidget4x4, mWidget2x3, mWidget1x1, mWidget2x4, mWidget2x2);
 
         List<ArrayList<WidgetItem>> widgetItemInTable =
-                WidgetsTableUtils.groupWidgetItemsIntoTableWithoutReordering(
-                        widgetItems, /* maxSpansPerRow= */ 5);
+                WidgetsTableUtils.groupWidgetItemsUsingRowPxWithoutReordering(widgetItems, mContext,
+                        mTestDeviceProfile, 220, 0);
 
-        // Row 0: 4x4
-        // Row 1: 2x3, 1x1
-        // Row 2: 2x4, 2x2
+        // Row 0: 4x4(230px)
+        // Row 1: 2x3(110px), 1x1(50px)
+        // Row 2: 2x4(110px), 2x2(110px)
         assertThat(widgetItemInTable).hasSize(3);
         assertThat(widgetItemInTable.get(0)).containsExactly(mWidget4x4);
         assertThat(widgetItemInTable.get(1)).containsExactly(mWidget2x3, mWidget1x1);
         assertThat(widgetItemInTable.get(2)).containsExactly(mWidget2x4, mWidget2x2);
     }
 
+    private void initDP() {
+        doAnswer(i -> {
+            ((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE);
+            return null;
+        }).when(mTestDeviceProfile).getCellSize(any(Point.class));
+        when(mTestDeviceProfile.getCellSize()).thenReturn(new Point(CELL_SIZE, CELL_SIZE));
+        mTestDeviceProfile.cellLayoutBorderSpacePx = new Point(SPACE_SIZE, SPACE_SIZE);
+        when(mTestDeviceProfile.shouldInsetWidgets()).thenReturn(false);
+    }
+
     private void initTestWidgets() {
         List<Point> widgetSizes = List.of(new Point(1, 1), new Point(2, 2), new Point(2, 3),
                 new Point(2, 4), new Point(4, 4));
@@ -184,7 +222,7 @@
                             LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
                     widgetInfo.spanX = widgetSize.x;
                     widgetInfo.spanY = widgetSize.y;
-                    widgetItems.add(new WidgetItem(widgetInfo, mTestProfile, mIconCache));
+                    widgetItems.add(new WidgetItem(widgetInfo, mTestInvariantProfile, mIconCache));
                 }
         );
         mWidget1x1 = widgetItems.get(0);