Merge "Correctly make to spec the color for "Turn on work apps" button and add stroke" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 9ff2cfc..5ddf2a8 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -299,7 +299,7 @@
         ItemInfo tag = (ItemInfo) v.getTag();
         if (tag != null && tag.shouldUseBackgroundAnimation()) {
             ContainerAnimationRunner containerAnimationRunner =
-                    ContainerAnimationRunner.from(v, mStartingWindowListener);
+                    ContainerAnimationRunner.from(v, mStartingWindowListener, onEndCallback);
             if (containerAnimationRunner != null) {
                 delegateRunner = containerAnimationRunner;
             }
@@ -1757,7 +1757,7 @@
 
         @Nullable
         private static ContainerAnimationRunner from(
-                View v, StartingWindowListener startingWindowListener) {
+                View v, StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
             View viewToUse = findViewWithBackground(v);
             if (viewToUse == null) {
                 viewToUse = v;
@@ -1784,8 +1784,15 @@
             ActivityLaunchAnimator.Callback callback = task -> ColorUtils.setAlphaComponent(
                     startingWindowListener.getBackgroundColor(), 255);
 
+            ActivityLaunchAnimator.Listener listener = new ActivityLaunchAnimator.Listener() {
+                @Override
+                public void onLaunchAnimationEnd() {
+                    onEndCallback.executeAllAndDestroy();
+                }
+            };
+
             return new ContainerAnimationRunner(
-                    new ActivityLaunchAnimator.AnimationDelegate(controller, callback));
+                    new ActivityLaunchAnimator.AnimationDelegate(controller, callback, listener));
         }
 
         /** Finds the closest parent of [view] (inclusive) with a background drawable. */
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 9f3be69..0dde1bd 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -118,7 +118,7 @@
     private void sendEvent(AppTarget target, LauncherAtom.ItemInfo locationInfo, int eventId,
             int targetPredictor) {
         // TODO: remove the running test check when b/231648228 is fixed.
-        if (target != null && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (target != null && !Utilities.isRunningInTestHarness()) {
             AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
                     .setLaunchLocation(getContainer(locationInfo))
                     .build();
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index b00c4cb..793c68e 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;
@@ -198,15 +197,7 @@
      */
     public Animator createAnimToLauncher(@NonNull LauncherState toState,
             @NonNull RecentsAnimationCallbacks callbacks, long duration) {
-        AnimatorSet set = new AnimatorSet();
-        Animator taskbarState = mTaskbarLauncherStateController
-                .createAnimToLauncher(toState, callbacks, duration);
-        long halfDuration = Math.round(duration * 0.5f);
-        Animator translation =
-                mControllers.taskbarTranslationController.createAnimToLauncher(halfDuration);
-
-        set.playTogether(taskbarState, translation);
-        return set;
+        return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
     }
 
     public boolean isDraggingItem() {
@@ -231,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
@@ -280,7 +264,7 @@
      * Returns {@code true} if a Taskbar education should be shown on application launch.
      */
     public boolean shouldShowEduOnAppLaunch() {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             return false;
         }
 
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 5c4f3e1..2864ac7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -24,7 +24,7 @@
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
-import static com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS;
+import static com.android.launcher3.Utilities.isRunningInTestHarness;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
@@ -337,8 +337,7 @@
         int windowFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                 | WindowManager.LayoutParams.FLAG_SLIPPERY
                 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-        if (DisplayController.isTransientTaskbar(this)
-                && !IS_RUNNING_IN_TEST_HARNESS) {
+        if (DisplayController.isTransientTaskbar(this) && !isRunningInTestHarness()) {
             windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                     | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
         }
@@ -877,7 +876,11 @@
      * as if the user tapped on it (preserving the split pair). Otherwise, launch it normally
      * (potentially breaking a split pair).
      */
-    private void launchFromTaskbarPreservingSplitIfVisible(RecentsView recents, ItemInfo info) {
+    private void launchFromTaskbarPreservingSplitIfVisible(@Nullable RecentsView recents,
+            ItemInfo info) {
+        if (recents == null) {
+            return;
+        }
         ComponentKey componentToBeLaunched = new ComponentKey(info.getTargetComponent(), info.user);
         recents.getSplitSelectController().findLastActiveTaskAndRunCallback(
                 componentToBeLaunched,
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/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 00dfaf2..bc582e2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -28,7 +28,6 @@
 import com.airbnb.lottie.model.KeyPath
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
-import com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
 import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_EDU_TOOLTIP
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
@@ -56,7 +55,8 @@
 class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
     LoggableTaskbarController {
 
-    private val isTooltipEnabled = !IS_RUNNING_IN_TEST_HARNESS && ENABLE_TASKBAR_EDU_TOOLTIP.get()
+    private val isTooltipEnabled: Boolean
+        get() = !Utilities.isRunningInTestHarness() && ENABLE_TASKBAR_EDU_TOOLTIP.get()
     private val isOpen: Boolean
         get() = tooltip?.isOpen ?: false
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index b586487..5ac0570 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -433,6 +433,14 @@
             });
             animatorSet.play(stashAnimator);
         }
+
+        if (isAnimatingToLauncher() || mLauncherState == LauncherState.NORMAL) {
+            // Translate back to 0 at a shorter or same duration as the icon alignment animation.
+            // This ensures there is no jump after switching to hotseat, e.g. when swiping up from
+            // overview to home. Currently we do duration / 2 just to make it feel snappier.
+            animatorSet.play(mControllers.taskbarTranslationController
+                    .createAnimToResetTranslation(duration / 2));
+        }
     }
 
     private boolean isInLauncher() {
@@ -460,7 +468,7 @@
         updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0);
 
         // Sync the first frame where we swap taskbar and hotseat.
-        if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) {
             ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(),
                     mControllers.taskbarActivityContext.getDragLayer(),
                     () -> {});
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 115db25..babafd5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -297,7 +297,7 @@
         }
         return supportsVisualStashing()
                 && isInApp()
-                && (!Utilities.IS_RUNNING_IN_TEST_HARNESS || mEnableManualStashingDuringTests)
+                && (!Utilities.isRunningInTestHarness() || mEnableManualStashingDuringTests)
                 && !DisplayController.isTransientTaskbar(mActivity);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
index a6b2a8a..062b4ce 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarTranslationController.java
@@ -135,9 +135,9 @@
     }
 
     /**
-     * Returns an animation to reset the taskbar translation for animation back to launcher.
+     * Returns an animation to reset the taskbar translation to {@code 0}.
      */
-    public ObjectAnimator createAnimToLauncher(long duration) {
+    public ObjectAnimator createAnimToResetTranslation(long duration) {
         ObjectAnimator animator = ObjectAnimator.ofFloat(mTranslationYForSwipe, VALUE, 0);
         animator.setInterpolator(Interpolators.LINEAR);
         animator.setDuration(duration);
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 ac92374..3143f23 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.AnimatedFloat.VALUE;
@@ -27,13 +28,15 @@
 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneMode;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
+import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM;
 
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.graphics.Rect;
-import android.util.FloatProperty;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
@@ -43,10 +46,10 @@
 import androidx.core.graphics.ColorUtils;
 import androidx.core.view.OneShotPreDrawListener;
 
-import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
+import com.android.launcher3.Reorderable;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.anim.AnimatedFloat;
@@ -56,7 +59,6 @@
 import com.android.launcher3.anim.RevealOutlineAnimation;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
@@ -65,6 +67,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.MultiValueAlpha;
 
 import java.io.PrintWriter;
@@ -338,18 +341,34 @@
                 croppedTransX = newLeft - iconLeft;
             }
 
-            as.play(ObjectAnimator.ofFloat(child, ICON_REVEAL_TRANSLATE_X, isStashed
-                    ? new float[] {croppedTransX}
-                    : new float[] {croppedTransX, 0}));
-
             float croppedTransY = child.getHeight() - stashedBounds.height();
-            as.play(ObjectAnimator.ofFloat(child, ICON_REVEAL_TRANSLATE_Y, isStashed
-                    ? new float[] {croppedTransY}
-                    : new float[] {croppedTransY, 0}));
-            as.addListener(forEndCallback(() -> {
-                ICON_REVEAL_TRANSLATE_X.set(child, 0f);
-                ICON_REVEAL_TRANSLATE_Y.set(child, 0f);
-            }));
+            if (child instanceof Reorderable) {
+                MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
+
+                as.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM),
+                        MULTI_PROPERTY_VALUE, isStashed
+                                ? new float[] {croppedTransX}
+                                : new float[] {croppedTransX, 0}));
+                as.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM),
+                        MULTI_PROPERTY_VALUE, isStashed
+                                ? new float[] {croppedTransY}
+                                : new float[] {croppedTransY, 0}));
+                as.addListener(forEndCallback(() ->
+                        mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0)));
+            } else {
+                as.play(ObjectAnimator.ofFloat(child,
+                        VIEW_TRANSLATE_X, isStashed
+                                ? new float[] {croppedTransX}
+                                : new float[] {croppedTransX, 0}));
+                as.play(ObjectAnimator.ofFloat(child,
+                        VIEW_TRANSLATE_Y, isStashed
+                                ? new float[] {croppedTransY}
+                                : new float[] {croppedTransY, 0}));
+                as.addListener(forEndCallback(() -> {
+                    child.setTranslationX(0);
+                    child.setTranslationY(0);
+                }));
+            }
         }
         return as;
     }
@@ -435,7 +454,7 @@
                 float childCenter = (child.getLeft() + child.getRight()) / 2f;
                 float halfQsbIconWidthDiff =
                         (launcherDp.hotseatQsbWidth - taskbarDp.iconSizePx) / 2f;
-                setter.addFloat(child, ICON_TRANSLATE_X,
+                setter.addFloat(child, VIEW_TRANSLATE_X,
                         isRtl ? -halfQsbIconWidthDiff : halfQsbIconWidthDiff,
                         hotseatIconCenter - childCenter, interpolator);
 
@@ -479,10 +498,18 @@
                     + hotseatCellSize / 2f;
 
             float childCenter = (child.getLeft() + child.getRight()) / 2f;
-            setter.setFloat(child, ICON_TRANSLATE_X, hotseatIconCenter - childCenter, interpolator);
+            if (child instanceof Reorderable) {
+                MultiTranslateDelegate mtd = ((Reorderable) child).getTranslateDelegate();
 
-            setter.setFloat(child, ICON_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
-
+                setter.setFloat(mtd.getTranslationX(INDEX_TASKBAR_ALIGNMENT_ANIM),
+                        MULTI_PROPERTY_VALUE, hotseatIconCenter - childCenter, interpolator);
+                setter.setFloat(mtd.getTranslationY(INDEX_TASKBAR_ALIGNMENT_ANIM),
+                        MULTI_PROPERTY_VALUE, mTaskbarBottomMargin, interpolator);
+            } else {
+                setter.setFloat(child, VIEW_TRANSLATE_X,
+                        hotseatIconCenter - childCenter, interpolator);
+                setter.setFloat(child, VIEW_TRANSLATE_Y, mTaskbarBottomMargin, interpolator);
+            }
             setter.setFloat(child, SCALE_PROPERTY, scaleUp, interpolator);
         }
 
@@ -667,107 +694,4 @@
             mControllers.uiController.onIconLayoutBoundsChanged();
         }
     }
-
-    public static final FloatProperty<View> ICON_TRANSLATE_X =
-            new FloatProperty<View>("taskbarAlignmentTranslateX") {
-
-                @Override
-                public void setValue(View view, float v) {
-                    if (view instanceof BubbleTextView) {
-                        ((BubbleTextView) view).setTranslationXForTaskbarAlignmentAnimation(v);
-                    } else if (view instanceof FolderIcon) {
-                        ((FolderIcon) view).setTranslationXForTaskbarAlignmentAnimation(v);
-                    } else {
-                        view.setTranslationX(v);
-                    }
-                }
-
-                @Override
-                public Float get(View view) {
-                    if (view instanceof BubbleTextView) {
-                        return ((BubbleTextView) view)
-                                .getTranslationXForTaskbarAlignmentAnimation();
-                    } else if (view instanceof FolderIcon) {
-                        return ((FolderIcon) view).getTranslationXForTaskbarAlignmentAnimation();
-                    }
-                    return view.getTranslationX();
-                }
-            };
-
-    public static final FloatProperty<View> ICON_TRANSLATE_Y =
-            new FloatProperty<View>("taskbarAlignmentTranslateY") {
-
-                @Override
-                public void setValue(View view, float v) {
-                    if (view instanceof BubbleTextView) {
-                        ((BubbleTextView) view).setTranslationYForTaskbarAlignmentAnimation(v);
-                    } else if (view instanceof FolderIcon) {
-                        ((FolderIcon) view).setTranslationYForTaskbarAlignmentAnimation(v);
-                    } else {
-                        view.setTranslationY(v);
-                    }
-                }
-
-                @Override
-                public Float get(View view) {
-                    if (view instanceof BubbleTextView) {
-                        return ((BubbleTextView) view)
-                                .getTranslationYForTaskbarAlignmentAnimation();
-                    } else if (view instanceof FolderIcon) {
-                        return ((FolderIcon) view).getTranslationYForTaskbarAlignmentAnimation();
-                    }
-                    return view.getTranslationY();
-                }
-            };
-
-    public static final FloatProperty<View> ICON_REVEAL_TRANSLATE_X =
-            new FloatProperty<View>("taskbarRevealTranslateX") {
-
-                @Override
-                public void setValue(View view, float v) {
-                    if (view instanceof BubbleTextView) {
-                        ((BubbleTextView) view).setTranslationXForTaskbarRevealAnimation(v);
-                    } else if (view instanceof FolderIcon) {
-                        ((FolderIcon) view).setTranslationXForTaskbarRevealAnimation(v);
-                    } else {
-                        view.setTranslationX(v);
-                    }
-                }
-
-                @Override
-                public Float get(View view) {
-                    if (view instanceof BubbleTextView) {
-                        return ((BubbleTextView) view).getTranslationXForTaskbarRevealAnimation();
-                    } else if (view instanceof FolderIcon) {
-                        return ((FolderIcon) view).getTranslationXForTaskbarRevealAnimation();
-                    }
-                    return view.getTranslationX();
-                }
-            };
-
-    public static final FloatProperty<View> ICON_REVEAL_TRANSLATE_Y =
-            new FloatProperty<View>("taskbarRevealTranslateY") {
-
-                @Override
-                public void setValue(View view, float v) {
-                    if (view instanceof BubbleTextView) {
-                        ((BubbleTextView) view).setTranslationYForTaskbarRevealAnimation(v);
-                    } else if (view instanceof FolderIcon) {
-                        ((FolderIcon) view).setTranslationYForTaskbarRevealAnimation(v);
-                    } else {
-                        view.setTranslationY(v);
-                    }
-                }
-
-                @Override
-                public Float get(View view) {
-                    if (view instanceof BubbleTextView) {
-                        return ((BubbleTextView) view).getTranslationYForTaskbarRevealAnimation();
-                    } else if (view instanceof FolderIcon) {
-                        return ((FolderIcon) view).getTranslationYForTaskbarRevealAnimation();
-                    }
-                    return view.getTranslationY();
-                }
-            };
-
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a56300a..a53f08a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -20,10 +20,11 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 
+import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
+import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -1053,7 +1054,8 @@
             activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
                     mLastTouchUpTime);
         }
-        if (item != null && item.itemType == ITEM_TYPE_SEARCH_ACTION) {
+        if (item != null && (item.animationType == DEFAULT_NO_ICON
+                || item.animationType == VIEW_BACKGROUND)) {
             activityOptions.options.setSplashScreenStyle(
                     SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
         } else {
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/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 07fcf48..f16b43d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -91,7 +91,7 @@
             builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
             mRecentsView.updateEmptyMessage();
             // TODO(b/246283207): Remove logging once root cause of flake detected.
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            if (Utilities.isRunningInTestHarness()) {
                 Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
                                 + mRecentsView.getCurrentPage()
                                 + ", getScrollForPage(getCurrentPage())): "
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 5eeeb36..a02f3de 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_HOME_DURATION;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -119,6 +120,12 @@
                 long scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
                         numPagesToScroll * PER_PAGE_SCROLL_DURATION);
                 config.duration = Math.max(config.duration, scrollDuration);
+
+                // Sync scroll so that it ends before or at the same time as the taskbar animation.
+                if (DisplayController.isTransientTaskbar(mActivity)
+                        && mActivity.getDeviceProfile().isTaskbarPresent) {
+                    config.duration = Math.min(config.duration, TASKBAR_TO_HOME_DURATION);
+                }
                 overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
             } else {
                 config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 0a155cb..4690d94 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -139,7 +139,7 @@
         if (IS_VERBOSE) {
             Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
         }
-        if (!Utilities.ATLEAST_R || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (!Utilities.ATLEAST_R || Utilities.isRunningInTestHarness()) {
             return;
         }
         SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_SNAPSHOT,
@@ -438,7 +438,7 @@
             }
 
             // TODO: remove this when b/231648228 is fixed.
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            if (Utilities.isRunningInTestHarness()) {
                 return;
             }
             int cardinality = mCardinality.orElseGet(() -> getCardinality(atomInfo));
@@ -636,7 +636,7 @@
     }
 
     private static int getCardinality(LauncherAtom.ItemInfo info) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             return 0;
         }
         switch (info.getContainerInfo().getContainerCase()) {
@@ -758,7 +758,7 @@
     }
 
     private static int getHierarchy(LauncherAtom.ItemInfo info) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             return 0;
         }
         if (info.getContainerInfo().getContainerCase() == FOLDER) {
@@ -801,7 +801,7 @@
     }
 
     private static int getSearchAttributes(LauncherAtom.ItemInfo info) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             return 0;
         }
         ContainerInfo containerInfo = info.getContainerInfo();
diff --git a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
index effdfdd..f6b2441 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherViewsMoveFromCenterTranslationApplier.java
@@ -15,12 +15,13 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_MOVE_FROM_CENTER_ANIM;
+
 import android.annotation.NonNull;
 import android.view.View;
 
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.widget.NavigableAppWidgetHostView;
+import com.android.launcher3.Reorderable;
+import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.TranslationApplier;
 
 /**
@@ -31,12 +32,9 @@
 
     @Override
     public void apply(@NonNull View view, float x, float y) {
-        if (view instanceof NavigableAppWidgetHostView) {
-            ((NavigableAppWidgetHostView) view).setTranslationForMoveFromCenterAnimation(x, y);
-        } else if (view instanceof BubbleTextView) {
-            ((BubbleTextView) view).setTranslationForMoveFromCenterAnimation(x, y);
-        } else if (view instanceof FolderIcon) {
-            ((FolderIcon) view).setTranslationForMoveFromCenterAnimation(x, y);
+        if (view instanceof Reorderable) {
+            MultiTranslateDelegate mtd = ((Reorderable) view).getTranslateDelegate();
+            mtd.setTranslation(INDEX_MOVE_FROM_CENTER_ANIM, x, y);
         } else {
             view.setTranslationX(x);
             view.setTranslationY(y);
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index 69ed2f8..4bc41bc 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -129,7 +129,7 @@
      * @param pointerIndex Index for the pointer being tracked in the motion event
      */
     public void addPosition(MotionEvent ev, int pointerIndex) {
-        long timeoutMs = Utilities.IS_RUNNING_IN_TEST_HARNESS
+        long timeoutMs = Utilities.isRunningInTestHarness()
                 ? TEST_HARNESS_TRIGGER_TIMEOUT
                 : mMakePauseHarderToTrigger
                         ? HARDER_TRIGGER_TIMEOUT
@@ -195,7 +195,7 @@
         if (mIsPaused != isPaused) {
             mIsPaused = isPaused;
             String logString = "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason;
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            if (Utilities.isRunningInTestHarness()) {
                 Log.d(TAG, logString);
             }
             ActiveGestureLog.INSTANCE.addLog(logString);
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index e928b27..cf07e6e 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -64,7 +64,7 @@
             });
         }
 
-        if (!Utilities.IS_RUNNING_IN_TEST_HARNESS
+        if (!Utilities.isRunningInTestHarness()
                 && !hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
                 boolean mFromAllApps = false;
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index e4c2dae..f94d80f 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -150,7 +150,7 @@
             // to other classes like PipTaskOrganizer / RecentsAnimationController to complete
             // the cleanup.
             if (SystemProperties.getBoolean(
-                    "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+                    "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
                 mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
                         mAppBounds, mActivityInfo);
             }  else {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 16935c1..ac59403 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -147,6 +147,8 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.DynamicResource;
@@ -1718,7 +1720,7 @@
             int finalTargetPage = targetPage;
             runOnPageScrollsInitialized(() -> {
                 // TODO(b/246283207): Remove logging once root cause of flake detected.
-                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                if (Utilities.isRunningInTestHarness()) {
                     Log.d("b/246283207", "RecentsView#applyLoadPlan() -> "
                             + "previousCurrentPage: " + previousCurrentPage
                             + ", targetPage: " + finalTargetPage
@@ -4491,6 +4493,7 @@
      * Attempts to initiate split with an existing taskView, if one exists
      */
     public void initiateSplitSelect(SplitSelectSource splitSelectSource) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "enterSplitSelect");
         mSplitSelectSource = splitSelectSource;
         mSplitHiddenTaskView = getTaskViewByTaskId(splitSelectSource.alreadyRunningTaskId);
         mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
new file mode 100644
index 0000000..f10b917
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.content.Intent;
+
+import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TaplTestsSplitscreen extends AbstractQuickStepTest {
+    private static final String CALCULATOR_APP_NAME = "Calculator";
+    private static final String CALCULATOR_APP_PACKAGE =
+            resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        TaplTestsLauncher3.initialize(this);
+
+        mLauncher.getWorkspace()
+                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
+                .switchToAllApps()
+                .getAppIcon(CALCULATOR_APP_NAME)
+                .dragToHotseat(0);
+
+        startAppFast(CALCULATOR_APP_PACKAGE);
+        mLauncher.enableBlockTimeout(true);
+        mLauncher.showTaskbarIfHidden();
+    }
+
+    @After
+    public void tearDown() {
+        mLauncher.enableBlockTimeout(false);
+    }
+
+    @Test
+    // TODO (b/270201357): When this test is proven stable, remove this TestStabilityRule and
+    // introduce into presubmit as well.
+    @TestStabilityRule.Stability(
+            flavors = TestStabilityRule.LOCAL | TestStabilityRule.PLATFORM_POSTSUBMIT)
+    @PortraitLandscape
+    @TaskbarModeSwitch
+    public void testSplitAppFromHomeWithItself() throws Exception {
+        Assume.assumeTrue(mLauncher.isTablet());
+
+        mLauncher.goHome()
+                .switchToAllApps()
+                .getAppIcon(CALCULATOR_APP_NAME)
+                .openMenu()
+                .getSplitScreenMenuItem()
+                .click();
+
+        mLauncher.getLaunchedAppState()
+                .getTaskbar()
+                .getAppIcon(CALCULATOR_APP_NAME)
+                .launchIntoSplitScreen();
+    }
+}
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/layout/widgets_full_sheet_paged_view.xml b/res/layout/widgets_full_sheet_paged_view.xml
index b02e3e3..455217f 100644
--- a/res/layout/widgets_full_sheet_paged_view.xml
+++ b/res/layout/widgets_full_sheet_paged_view.xml
@@ -23,19 +23,20 @@
         android:clipToPadding="false"
         android:layout_below="@id/collapse_handle"
         android:descendantFocusability="afterDescendants"
-        android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
         launcher:pageIndicator="@+id/tabs" >
 
         <com.android.launcher3.widget.picker.WidgetsRecyclerView
             android:id="@+id/primary_widgets_list_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:clipToPadding="false" />
 
         <com.android.launcher3.widget.picker.WidgetsRecyclerView
             android:id="@+id/work_widgets_list_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
+            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:clipToPadding="false" />
 
     </com.android.launcher3.widget.picker.WidgetPagedView>
@@ -47,6 +48,7 @@
         android:layout_height="wrap_content"
         android:layout_below="@id/collapse_handle"
         android:paddingBottom="0dp"
+        android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
         android:clipToOutline="true"
         android:orientation="vertical">
 
@@ -57,7 +59,6 @@
             android:gravity="center_horizontal"
             android:textSize="24sp"
             android:layout_marginTop="24dp"
-            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             android:textColor="?android:attr/textColorSecondary"
             android:text="@string/widget_button_text"/>
 
@@ -68,7 +69,6 @@
             android:elevation="0.1dp"
             android:background="?android:attr/colorBackground"
             android:paddingBottom="8dp"
-            android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
             launcher:layout_sticky="true">
             <include layout="@layout/widgets_search_bar" />
         </FrameLayout>
@@ -80,7 +80,6 @@
             android:layout_marginTop="8dp"
             android:background="@drawable/widgets_surface_background"
             android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
-            android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
             android:visibility="gone" />
 
         <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
@@ -90,7 +89,6 @@
             android:gravity="center_horizontal"
             android:orientation="horizontal"
             android:paddingVertical="8dp"
-            android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
             android:background="?android:attr/colorBackground"
             style="@style/TextHeadline"
             launcher:layout_sticky="true">
diff --git a/res/layout/widgets_full_sheet_recyclerview.xml b/res/layout/widgets_full_sheet_recyclerview.xml
index 366d2d2..887f00c 100644
--- a/res/layout/widgets_full_sheet_recyclerview.xml
+++ b/res/layout/widgets_full_sheet_recyclerview.xml
@@ -20,7 +20,7 @@
         android:layout_below="@id/collapse_handle"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
+        android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
         android:clipToPadding="false" />
 
     <!-- SearchAndRecommendationsView without the tab layout as well -->
@@ -30,7 +30,7 @@
         android:layout_height="wrap_content"
         android:layout_below="@id/collapse_handle"
         android:paddingBottom="16dp"
-        android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
+        android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin"
         android:clipToOutline="true"
         android:orientation="vertical">
 
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/colors.xml b/res/values/colors.xml
index 3b3c43b..8788557 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -80,9 +80,7 @@
     <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>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 94b8cd8..bc4a5c3 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -827,6 +827,6 @@
     private boolean hasSeenReconfigurableWidgetEducationTip() {
         return mLauncher.getSharedPrefs()
                 .getBoolean(KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN, false)
-                || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+                || Utilities.isRunningInTestHarness();
     }
 }
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index df38c26..3eb03ed 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -34,7 +34,6 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -71,6 +70,7 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.views.ActivityContext;
@@ -100,21 +100,8 @@
 
     private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
 
-    private final PointF mTranslationForReorderBounce = new PointF(0, 0);
-    private final PointF mTranslationForReorderPreview = new PointF(0, 0);
-
-    private float mTranslationXForTaskbarAlignmentAnimation = 0f;
-    private float mTranslationYForTaskbarAlignmentAnimation = 0f;
-
-    private float mTranslationXForTaskbarRevealAnimation = 0f;
-    private float mTranslationYForTaskbarRevealAnimation = 0f;
-
-    private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
-
     private float mScaleForReorderBounce = 1f;
 
-    private float mTranslationXForTaskbarAllAppsIcon = 0f;
-
     private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
             = new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
         @Override
@@ -142,6 +129,7 @@
         }
     };
 
+    private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
     private final ActivityContext mActivity;
     private FastBitmapDrawable mIcon;
     private boolean mCenterVertically;
@@ -960,131 +948,23 @@
                 mDisplay == DISPLAY_SEARCH_RESULT_SMALL;
     }
 
-    private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x
-                + mTranslationForReorderPreview.x
-                + mTranslationXForTaskbarAllAppsIcon
-                + mTranslationForMoveFromCenterAnimation.x
-                + mTranslationXForTaskbarAlignmentAnimation
-                + mTranslationXForTaskbarRevealAnimation
-        );
-        super.setTranslationY(mTranslationForReorderBounce.y
-                + mTranslationForReorderPreview.y
-                + mTranslationForMoveFromCenterAnimation.y
-                + mTranslationYForTaskbarAlignmentAnimation
-                + mTranslationYForTaskbarRevealAnimation);
-    }
-
-    /**
-     * Sets translationX for taskbar all apps icon
-     */
-    public void setTranslationXForTaskbarAllAppsIcon(float translationX) {
-        mTranslationXForTaskbarAllAppsIcon = translationX;
-        updateTranslation();
-    }
-
-    public void setReorderBounceOffset(float x, float y) {
-        mTranslationForReorderBounce.set(x, y);
-        updateTranslation();
-    }
-
-    public void getReorderBounceOffset(PointF offset) {
-        offset.set(mTranslationForReorderBounce);
+    @Override
+    public MultiTranslateDelegate getTranslateDelegate() {
+        return mTranslateDelegate;
     }
 
     @Override
-    public void setReorderPreviewOffset(float x, float y) {
-        mTranslationForReorderPreview.set(x, y);
-        updateTranslation();
-    }
-
-    @Override
-    public void getReorderPreviewOffset(PointF offset) {
-        offset.set(mTranslationForReorderPreview);
-    }
-
     public void setReorderBounceScale(float scale) {
         mScaleForReorderBounce = scale;
         super.setScaleX(scale);
         super.setScaleY(scale);
     }
 
+    @Override
     public float getReorderBounceScale() {
         return mScaleForReorderBounce;
     }
 
-    /**
-     * Sets translation values for move from center animation
-     */
-    public void setTranslationForMoveFromCenterAnimation(float x, float y) {
-        mTranslationForMoveFromCenterAnimation.set(x, y);
-        updateTranslation();
-    }
-
-    /**
-     * Sets translationX for taskbar to launcher alignment animation
-     */
-    public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
-        mTranslationXForTaskbarAlignmentAnimation = translationX;
-        updateTranslation();
-    }
-
-    /**
-     * Returns translationX value for taskbar to launcher alignment animation
-     */
-    public float getTranslationXForTaskbarAlignmentAnimation() {
-        return mTranslationXForTaskbarAlignmentAnimation;
-    }
-
-    /**
-     * Sets translationX for taskbar to launcher alignment animation
-     */
-    public void setTranslationYForTaskbarAlignmentAnimation(float translationY) {
-        mTranslationYForTaskbarAlignmentAnimation = translationY;
-        updateTranslation();
-    }
-
-    /**
-     * Returns translationY value for taskbar to launcher alignment animation
-     */
-    public float getTranslationYForTaskbarAlignmentAnimation() {
-        return mTranslationYForTaskbarAlignmentAnimation;
-    }
-
-    /**
-     * Sets translationX value for taskbar reveal animation
-     */
-    public void setTranslationXForTaskbarRevealAnimation(float translationX) {
-        mTranslationXForTaskbarRevealAnimation = translationX;
-        updateTranslation();
-    }
-
-    /**
-     * Returns translation values for taskbar reveal animation
-     */
-    public float getTranslationXForTaskbarRevealAnimation() {
-        return mTranslationXForTaskbarRevealAnimation;
-    }
-
-    /**
-     * Sets translationY value for taskbar reveal animation
-     */
-    public void setTranslationYForTaskbarRevealAnimation(float translationY) {
-        mTranslationYForTaskbarRevealAnimation = translationY;
-        updateTranslation();
-    }
-
-    /**
-     * Returns translationY values for taskbar reveal animation
-     */
-    public float getTranslationYForTaskbarRevealAnimation() {
-        return mTranslationYForTaskbarRevealAnimation;
-    }
-
-    public View getView() {
-        return this;
-    }
-
     @Override
     public int getViewType() {
         return DRAGGABLE_ICON;
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index b96e4df..38b0e08 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -22,6 +22,8 @@
 import static com.android.launcher3.config.FeatureFlags.SHOW_HOME_GARDENING;
 import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_BOUNCE_OFFSET;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -63,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;
@@ -70,6 +73,7 @@
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.util.CellAndSpan;
 import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.ParcelableSparseArray;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
@@ -112,7 +116,7 @@
     final PointF mTmpPointF = new PointF();
 
     protected GridOccupancy mOccupied;
-    protected GridOccupancy mTmpOccupied;
+    public GridOccupancy mTmpOccupied;
 
     private OnTouchListener mInterceptTouchListener;
 
@@ -191,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;
@@ -1099,13 +1103,12 @@
             lp.isLockedToGrid = false;
             // End compute new x and y
 
-            item.getReorderPreviewOffset(mTmpPointF);
-            final float initPreviewOffsetX = mTmpPointF.x;
-            final float initPreviewOffsetY = mTmpPointF.y;
+            MultiTranslateDelegate mtd = item.getTranslateDelegate();
+            float initPreviewOffsetX = mtd.getTranslationX(INDEX_REORDER_PREVIEW_OFFSET).getValue();
+            float initPreviewOffsetY = mtd.getTranslationY(INDEX_REORDER_PREVIEW_OFFSET).getValue();
             final float finalPreviewOffsetX = newX - oldX;
             final float finalPreviewOffsetY = newY - oldY;
 
-
             // Exit early if we're not actually moving the view
             if (finalPreviewOffsetX == 0 && finalPreviewOffsetY == 0
                     && initPreviewOffsetX == 0 && initPreviewOffsetY == 0) {
@@ -1123,7 +1126,7 @@
                     float r = (Float) animation.getAnimatedValue();
                     float x = (1 - r) * initPreviewOffsetX + r * finalPreviewOffsetX;
                     float y = (1 - r) * initPreviewOffsetY + r * finalPreviewOffsetY;
-                    item.setReorderPreviewOffset(x, y);
+                    item.getTranslateDelegate().setTranslation(INDEX_REORDER_PREVIEW_OFFSET, x, y);
                 }
             });
             va.addListener(new AnimatorListenerAdapter() {
@@ -1134,7 +1137,8 @@
                     // place just yet.
                     if (!cancelled) {
                         lp.isLockedToGrid = true;
-                        item.setReorderPreviewOffset(0, 0);
+                        item.getTranslateDelegate()
+                                .setTranslation(INDEX_REORDER_PREVIEW_OFFSET, 0, 0);
                         child.requestLayout();
                     }
                     if (mReorderAnimators.containsKey(lp)) {
@@ -1243,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);
     }
@@ -1377,6 +1381,10 @@
         return bestXY;
     }
 
+    public GridOccupancy getOccupied() {
+        return mOccupied;
+    }
+
     private void copySolutionToTempState(ItemConfiguration solution, View dragView) {
         mTmpOccupied.clear();
 
@@ -1434,7 +1442,7 @@
 
             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
             if (c != null && !skip && (child instanceof Reorderable)) {
-                ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
+                ReorderPreviewAnimation rha = new ReorderPreviewAnimation(child,
                         mode, lp.getCellX(), lp.getCellY(), c.cellX, c.cellY, c.spanX, c.spanY);
                 rha.animate();
             }
@@ -1456,8 +1464,8 @@
 
     // Class which represents the reorder preview animations. These animations show that an item is
     // in a temporary state, and hint at where the item will return to.
-    class ReorderPreviewAnimation {
-        final Reorderable child;
+    class ReorderPreviewAnimation<T extends View & Reorderable> {
+        final T child;
         float finalDeltaX;
         float finalDeltaY;
         float initDeltaX;
@@ -1477,7 +1485,7 @@
         float animationProgress = 0;
         ValueAnimator a;
 
-        public ReorderPreviewAnimation(Reorderable child, int mode, int cellX0, int cellY0,
+        ReorderPreviewAnimation(View childView, int mode, int cellX0, int cellY0,
                 int cellX1, int cellY1, int spanX, int spanY) {
             regionToCenterPoint(cellX0, cellY0, spanX, spanY, mTmpPoint);
             final int x0 = mTmpPoint[0];
@@ -1488,16 +1496,16 @@
             final int dX = x1 - x0;
             final int dY = y1 - y0;
 
-            this.child = child;
+            this.child = (T) childView;
             this.mode = mode;
             finalDeltaX = 0;
             finalDeltaY = 0;
 
-            child.getReorderBounceOffset(mTmpPointF);
-            initDeltaX = mTmpPointF.x;
-            initDeltaY = mTmpPointF.y;
+            MultiTranslateDelegate mtd = child.getTranslateDelegate();
+            initDeltaX = mtd.getTranslationX(INDEX_REORDER_BOUNCE_OFFSET).getValue();
+            initDeltaY = mtd.getTranslationY(INDEX_REORDER_BOUNCE_OFFSET).getValue();
             initScale = child.getReorderBounceScale();
-            finalScale = mChildScale - (CHILD_DIVIDEND / child.getView().getWidth()) * initScale;
+            finalScale = mChildScale - (CHILD_DIVIDEND / child.getWidth()) * initScale;
 
             int dir = mode == MODE_HINT ? -1 : 1;
             if (dX == dY && dX == 0) {
@@ -1573,7 +1581,7 @@
             float r1 = (mode == MODE_HINT && repeating) ? 1.0f : animationProgress;
             float x = r1 * finalDeltaX + (1 - r1) * initDeltaX;
             float y = r1 * finalDeltaY + (1 - r1) * initDeltaY;
-            child.setReorderBounceOffset(x, y);
+            child.getTranslateDelegate().setTranslation(INDEX_REORDER_BOUNCE_OFFSET, x, y);
             float s = animationProgress * finalScale + (1 - animationProgress) * initScale;
             child.setReorderBounceScale(s);
         }
@@ -1652,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);
@@ -1708,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,
@@ -2254,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
@@ -2346,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;
@@ -2402,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);
@@ -2466,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.
@@ -2511,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,
@@ -2585,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);
@@ -2631,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/Launcher.java b/src/com/android/launcher3/Launcher.java
index de60d05..22b07ef 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1647,7 +1647,7 @@
 
     @Override
     protected void onNewIntent(Intent intent) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Launcher.onNewIntent: " + intent);
         }
         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
@@ -3172,7 +3172,7 @@
 
                 // Setting the touch point to (-1, -1) will show the options popup in the center of
                 // the screen.
-                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                if (Utilities.isRunningInTestHarness()) {
                     Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
                 }
                 showDefaultOptions(-1, -1);
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 66ea616..76cae6a 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -41,6 +41,10 @@
          * An animation using the view's background.
          */
         public static final int VIEW_BACKGROUND = 1;
+        /**
+         * The default animation for a given view/item info type, but without the splash icon.
+         */
+        public static final int DEFAULT_NO_ICON = 2;
     }
 
     /**
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 c3d8a53..c7431ed 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -773,7 +773,7 @@
 
         if (mScroller.isFinished() && pageScrollChanged) {
             // TODO(b/246283207): Remove logging once root cause of flake detected.
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS && !(this instanceof Workspace)) {
+            if (Utilities.isRunningInTestHarness() && !(this instanceof Workspace)) {
                 Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> "
                         + "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): "
                         + getNextPage() + ", getScrollForPage(getNextPage()): "
@@ -1713,7 +1713,7 @@
             return false;
         }
 
-        if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.isRunningInTestHarness()) {
             duration *= Settings.Global.getFloat(getContext().getContentResolver(),
                     Settings.Global.WINDOW_ANIMATION_SCALE, 1);
         }
diff --git a/src/com/android/launcher3/Reorderable.java b/src/com/android/launcher3/Reorderable.java
index 047fb01..5afd95d 100644
--- a/src/com/android/launcher3/Reorderable.java
+++ b/src/com/android/launcher3/Reorderable.java
@@ -16,33 +16,19 @@
 
 package com.android.launcher3;
 
-import android.graphics.PointF;
-import android.view.View;
+import com.android.launcher3.util.MultiTranslateDelegate;
 
 public interface Reorderable {
 
     /**
-     * Set the offset related to reorder hint and bounce animations
+     * Returns the delegate to control translation
      */
-    void setReorderBounceOffset(float x, float y);
-
-    void getReorderBounceOffset(PointF offset);
-
-    /**
-     * Set the offset related to previewing the new reordered position
-     */
-    void setReorderPreviewOffset(float x, float y);
-
-    void getReorderPreviewOffset(PointF offset);
+    MultiTranslateDelegate getTranslateDelegate();
 
     /**
      * Set the scale related to reorder hint and "bounce" animations
      */
     void setReorderBounceScale(float scale);
-    float getReorderBounceScale();
 
-    /**
-     * Get the com.android.view related to this object
-     */
-    View getView();
+    float getReorderBounceScale();
 }
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 55b745b..b00199f 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.CellLayout.FOLDER;
 import static com.android.launcher3.CellLayout.HOTSEAT;
 import static com.android.launcher3.CellLayout.WORKSPACE;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_WIDGET_CENTERING;
 
 import android.app.WallpaperManager;
 import android.content.Context;
@@ -208,7 +209,8 @@
             float scaleY = appWidgetScale.y;
 
             nahv.setScaleToFit(Math.min(scaleX, scaleY));
-            nahv.setTranslationForCentering(-(lp.width - (lp.width * scaleX)) / 2.0f,
+            nahv.getTranslateDelegate().setTranslation(INDEX_WIDGET_CENTERING,
+                    -(lp.width - (lp.width * scaleX)) / 2.0f,
                     -(lp.height - (lp.height * scaleY)) / 2.0f);
         }
 
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7d01f7b..59327dc 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -150,11 +150,14 @@
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
     }
 
-    public static boolean IS_RUNNING_IN_TEST_HARNESS =
-                    ActivityManager.isRunningInTestHarness();
+    private static boolean sIsRunningInTestHarness = ActivityManager.isRunningInTestHarness();
+
+    public static boolean isRunningInTestHarness() {
+        return sIsRunningInTestHarness;
+    }
 
     public static void enableRunningInTestHarnessForTests() {
-        IS_RUNNING_IN_TEST_HARNESS = true;
+        sIsRunningInTestHarness = true;
     }
 
     public static boolean isPropertyEnabled(String propertyName) {
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 0188a47..df22425 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -127,7 +127,7 @@
                 || onboardingPrefs.getBoolean(OnboardingPrefs.HOME_BOUNCE_SEEN)
                 || AbstractFloatingView.getTopOpenView(launcher) != null
                 || launcher.getSystemService(UserManager.class).isDemoUser()
-                || Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                || Utilities.isRunningInTestHarness()) {
             return;
         }
 
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/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 770e931..24cc0ac 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -127,7 +127,7 @@
      */
     private static AccessibilityManager getAccessibilityManagerForTest(Context context) {
         // If not running in a test harness, don't participate in test exchanges.
-        if (!Utilities.IS_RUNNING_IN_TEST_HARNESS) return null;
+        if (!Utilities.isRunningInTestHarness()) return null;
 
         final AccessibilityManager accessibilityManager = getManager(context);
         if (!accessibilityManager.isEnabled()) return null;
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
index 08e50dd..0e76bbb 100644
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
@@ -43,7 +43,7 @@
 
     private long getEnterSpringLoadHoverTime() {
         // Some TAPL tests are flaky on Cuttlefish with a low waiting time
-        return Utilities.IS_RUNNING_IN_TEST_HARNESS
+        return Utilities.isRunningInTestHarness()
                 ? ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
                 : ENTER_SPRING_LOAD_HOVER_TIME;
     }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index ee1a060..86f4beb 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -28,7 +28,6 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -77,6 +76,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.IconLabelDotView;
@@ -93,6 +93,7 @@
 public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
         DraggableView, Reorderable {
 
+    private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
     @Thunk ActivityContext mActivity;
     @Thunk Folder mFolder;
     public FolderInfo mInfo;
@@ -133,14 +134,6 @@
 
     private Rect mTouchArea = new Rect();
 
-    private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
-    private float mTranslationXForTaskbarAlignmentAnimation = 0f;
-    private float mTranslationYForTaskbarAlignmentAnimation = 0f;
-    private float mTranslationXForTaskbarRevealAnimation = 0f;
-    private float mTranslationYForTaskbarRevealAnimation = 0f;
-
-    private final PointF mTranslationForReorderBounce = new PointF(0, 0);
-    private final PointF mTranslationForReorderPreview = new PointF(0, 0);
     private float mScaleForReorderBounce = 1f;
 
     private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
@@ -770,120 +763,23 @@
         mPreviewItemManager.onFolderClose(currentPage);
     }
 
-    private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x
-                + mTranslationForReorderPreview.x
-                + mTranslationForMoveFromCenterAnimation.x
-                + mTranslationXForTaskbarAlignmentAnimation
-                + mTranslationXForTaskbarRevealAnimation);
-        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
-                + mTranslationForMoveFromCenterAnimation.y
-                + mTranslationYForTaskbarAlignmentAnimation
-                + mTranslationYForTaskbarRevealAnimation);
-    }
-
-    public void setReorderBounceOffset(float x, float y) {
-        mTranslationForReorderBounce.set(x, y);
-        updateTranslation();
-    }
-
-    public void getReorderBounceOffset(PointF offset) {
-        offset.set(mTranslationForReorderBounce);
-    }
-
-    /**
-     * Sets translationX value for taskbar to launcher alignment animation
-     */
-    public void setTranslationXForTaskbarAlignmentAnimation(float translationX) {
-        mTranslationXForTaskbarAlignmentAnimation = translationX;
-        updateTranslation();
-    }
-
-    /**
-     * Returns translation values for taskbar to launcher alignment animation
-     */
-    public float getTranslationXForTaskbarAlignmentAnimation() {
-        return mTranslationXForTaskbarAlignmentAnimation;
-    }
-
-    /**
-     * Sets translationY value for taskbar to launcher alignment animation
-     */
-    public void setTranslationYForTaskbarAlignmentAnimation(float translationY) {
-        mTranslationYForTaskbarAlignmentAnimation = translationY;
-        updateTranslation();
-    }
-
-    /**
-     * Returns translation values for taskbar to launcher alignment animation
-     */
-    public float getTranslationYForTaskbarAlignmentAnimation() {
-        return mTranslationYForTaskbarAlignmentAnimation;
-    }
-
-    /**
-     * Sets translationX value for taskbar reveal animation
-     */
-    public void setTranslationXForTaskbarRevealAnimation(float translationX) {
-        mTranslationXForTaskbarRevealAnimation = translationX;
-        updateTranslation();
-    }
-
-    /**
-     * Returns translation values for taskbar reveal animation
-     */
-    public float getTranslationXForTaskbarRevealAnimation() {
-        return mTranslationXForTaskbarRevealAnimation;
-    }
-
-    /**
-     * Sets translationY value for taskbar reveal animation
-     */
-    public void setTranslationYForTaskbarRevealAnimation(float translationY) {
-        mTranslationYForTaskbarRevealAnimation = translationY;
-        updateTranslation();
-    }
-
-    /**
-     * Returns translationY values for taskbar reveal animation
-     */
-    public float getTranslationYForTaskbarRevealAnimation() {
-        return mTranslationYForTaskbarRevealAnimation;
-    }
-
-    /**
-     * Sets translation values for move from center animation
-     */
-    public void setTranslationForMoveFromCenterAnimation(float x, float y) {
-        mTranslationForMoveFromCenterAnimation.set(x, y);
-        updateTranslation();
+    @Override
+    public MultiTranslateDelegate getTranslateDelegate() {
+        return mTranslateDelegate;
     }
 
     @Override
-    public void setReorderPreviewOffset(float x, float y) {
-        mTranslationForReorderPreview.set(x, y);
-        updateTranslation();
-    }
-
-    @Override
-    public void getReorderPreviewOffset(PointF offset) {
-        offset.set(mTranslationForReorderPreview);
-    }
-
     public void setReorderBounceScale(float scale) {
         mScaleForReorderBounce = scale;
         super.setScaleX(scale);
         super.setScaleY(scale);
     }
 
+    @Override
     public float getReorderBounceScale() {
         return mScaleForReorderBounce;
     }
 
-    public View getView() {
-        return this;
-    }
-
     @Override
     public int getViewType() {
         return DRAGGABLE_ICON;
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index 5444d92..17b472a 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -61,7 +61,7 @@
 
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
             handler.init(getContext());
 
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index c151606..f95548d 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -39,13 +39,13 @@
     }
 
     public static void recordEvent(String sequence, String event) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             recordEventSlow(sequence, event);
         }
     }
 
     public static void recordEvent(String sequence, String message, Object parameter) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             recordEventSlow(sequence, message + ": " + parameter);
         }
     }
@@ -58,14 +58,14 @@
     }
 
     public static void recordKeyEvent(String sequence, String message, KeyEvent event) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+        if (Utilities.isRunningInTestHarness()) {
             recordEventSlow(sequence, message + ": " + event);
             registerEventNotFromTest(event);
         }
     }
 
     public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS && event.getAction() != MotionEvent.ACTION_MOVE) {
+        if (Utilities.isRunningInTestHarness() && event.getAction() != MotionEvent.ACTION_MOVE) {
             recordEventSlow(sequence, message + ": " + event);
             registerEventNotFromTest(event);
         }
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 9ed6700..3d455d8 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -139,11 +139,11 @@
         // TODO(b/258604917): When running in test harness, use !sTransientTaskbarStatusForTests
         //  once tests are updated to expect new persistent behavior such as not allowing long press
         //  to stash.
-        if (!Utilities.IS_RUNNING_IN_TEST_HARNESS && FORCE_PERSISTENT_TASKBAR.get()) {
+        if (!Utilities.isRunningInTestHarness() && FORCE_PERSISTENT_TASKBAR.get()) {
             return false;
         }
         return getInfo().navigationMode == NavigationMode.NO_BUTTON
-                && (Utilities.IS_RUNNING_IN_TEST_HARNESS
+                && (Utilities.isRunningInTestHarness()
                     ? sTransientTaskbarStatusForTests
                     : ENABLE_TRANSIENT_TASKBAR.get());
     }
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
new file mode 100644
index 0000000..0b5bc8d
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+
+import android.view.View;
+
+import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
+
+/**
+ * A utility class to split translation components for various workspace items
+ */
+public class MultiTranslateDelegate {
+
+    // offset related to reorder hint and bounce animations
+    public static final int INDEX_REORDER_BOUNCE_OFFSET = 0;
+    // offset related to previewing the new reordered position
+    public static final int INDEX_REORDER_PREVIEW_OFFSET = 1;
+    public static final int INDEX_MOVE_FROM_CENTER_ANIM = 2;
+
+    // Specific for icons and folders
+    public static final int INDEX_TASKBAR_ALIGNMENT_ANIM = 3;
+    public static final int INDEX_TASKBAR_REVEAL_ANIM = 4;
+
+    // Specific for widgets
+    public static final int INDEX_WIDGET_CENTERING = 3;
+
+    public static final int COUNT = 5;
+
+    private final MultiPropertyFactory<View> mTranslationX;
+    private final MultiPropertyFactory<View> mTranslationY;
+
+    public MultiTranslateDelegate(View target) {
+        this(target, COUNT, COUNT);
+    }
+
+    public MultiTranslateDelegate(View target, int countX, int countY) {
+        mTranslationX = new MultiPropertyFactory<>(target, VIEW_TRANSLATE_X, countX, Float::sum);
+        mTranslationY = new MultiPropertyFactory<>(target, VIEW_TRANSLATE_Y, countY, Float::sum);
+    }
+
+    /**
+     * Helper method to set both translations, x and y at a given index
+     */
+    public void setTranslation(int index, float x, float y) {
+        getTranslationX(index).setValue(x);
+        getTranslationY(index).setValue(y);
+    }
+
+    /**
+     * Returns the translation x for the provided index
+     */
+    public MultiProperty getTranslationX(int index) {
+        return mTranslationX.get(index);
+    }
+
+    /**
+     * Returns the translation y for the provided index
+     */
+    public MultiProperty getTranslationY(int index) {
+        return mTranslationY.get(index);
+    }
+}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index b6f6223..10f40b7 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -45,6 +45,7 @@
 import android.view.WindowInsetsController;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Toast;
+import android.window.SplashScreen;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -320,7 +321,14 @@
             return false;
         }
 
-        Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v, item).toBundle() : null;
+        Bundle optsBundle = null;
+        if (v != null) {
+            optsBundle = getActivityLaunchOptions(v, item).toBundle();
+        } else if (item != null && item.animationType == LauncherSettings.Animation.DEFAULT_NO_ICON
+                && Utilities.ATLEAST_T) {
+            optsBundle = ActivityOptions.makeBasic()
+                    .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR).toBundle();
+        }
         UserHandle user = item == null ? null : item.user;
 
         // Prepare intent
diff --git a/src/com/android/launcher3/views/IconButtonView.java b/src/com/android/launcher3/views/IconButtonView.java
index 9969eeb..71f6756 100644
--- a/src/com/android/launcher3/views/IconButtonView.java
+++ b/src/com/android/launcher3/views/IconButtonView.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.util.MultiTranslateDelegate;
 
 /**
  * Button in Taskbar that shows a tinted background and foreground.
@@ -45,6 +46,12 @@
 
     private static final int[] ATTRS = {android.R.attr.icon};
 
+    private static final int INDEX_TASKBAR_ALL_APPS_ICON = MultiTranslateDelegate.COUNT;
+    private static final int MY_COUNT = MultiTranslateDelegate.COUNT + 1;
+
+    private final MultiTranslateDelegate mTranslateDelegate =
+            new MultiTranslateDelegate(this, MY_COUNT, MultiTranslateDelegate.COUNT);
+
     public IconButtonView(Context context) {
         this(context, null);
     }
@@ -88,6 +95,18 @@
         }
     }
 
+    @Override
+    public MultiTranslateDelegate getTranslateDelegate() {
+        return mTranslateDelegate;
+    }
+
+    /**
+     * Sets translationX for taskbar all apps icon
+     */
+    public void setTranslationXForTaskbarAllAppsIcon(float translationX) {
+        getTranslateDelegate().getTranslationX(INDEX_TASKBAR_ALL_APPS_ICON).setValue(translationX);
+    }
+
     private static class IconDrawable extends FastBitmapDrawable {
 
         private final Drawable mFg;
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 2ac1e94..68ece03 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -341,7 +341,7 @@
     /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */
     protected boolean hasSeenEducationTip() {
         return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false)
-                || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+                || Utilities.isRunningInTestHarness();
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
index 241c937..3389fb1 100644
--- a/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/NavigableAppWidgetHostView.java
@@ -19,7 +19,6 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.view.KeyEvent;
 import android.view.View;
@@ -29,6 +28,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Reorderable;
 import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
@@ -39,20 +39,13 @@
 public abstract class NavigableAppWidgetHostView extends AppWidgetHostView
         implements DraggableView, Reorderable {
 
+    private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
+
     /**
      * The scaleX and scaleY value such that the widget fits within its cellspans, scaleX = scaleY.
      */
     private float mScaleToFit = 1f;
 
-    /**
-     * The translation values to center the widget within its cellspans.
-     */
-    private final PointF mTranslationForCentering = new PointF(0, 0);
-
-    private final PointF mTranslationForMoveFromCenterAnimation = new PointF(0, 0);
-
-    private final PointF mTranslationForReorderBounce = new PointF(0, 0);
-    private final PointF mTranslationForReorderPreview = new PointF(0, 0);
     private float mScaleForReorderBounce = 1f;
 
     private final Rect mTempRect = new Rect();
@@ -163,57 +156,23 @@
         setSelected(childIsFocused);
     }
 
-    public View getView() {
-        return this;
-    }
-
-    private void updateTranslation() {
-        super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x
-                + mTranslationForCentering.x + mTranslationForMoveFromCenterAnimation.x);
-        super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y
-                + mTranslationForCentering.y + mTranslationForMoveFromCenterAnimation.y);
-    }
-
-    public void setTranslationForCentering(float x, float y) {
-        mTranslationForCentering.set(x, y);
-        updateTranslation();
-    }
-
-    public void setTranslationForMoveFromCenterAnimation(float x, float y) {
-        mTranslationForMoveFromCenterAnimation.set(x, y);
-        updateTranslation();
-    }
-
-    public void setReorderBounceOffset(float x, float y) {
-        mTranslationForReorderBounce.set(x, y);
-        updateTranslation();
-    }
-
-    public void getReorderBounceOffset(PointF offset) {
-        offset.set(mTranslationForReorderBounce);
-    }
-
-    @Override
-    public void setReorderPreviewOffset(float x, float y) {
-        mTranslationForReorderPreview.set(x, y);
-        updateTranslation();
-    }
-
-    @Override
-    public void getReorderPreviewOffset(PointF offset) {
-        offset.set(mTranslationForReorderPreview);
-    }
-
     private void updateScale() {
         super.setScaleX(mScaleToFit * mScaleForReorderBounce);
         super.setScaleY(mScaleToFit * mScaleForReorderBounce);
     }
 
+    @Override
+    public MultiTranslateDelegate getTranslateDelegate() {
+        return mTranslateDelegate;
+    }
+
+    @Override
     public void setReorderBounceScale(float scale) {
         mScaleForReorderBounce = scale;
         updateScale();
     }
 
+    @Override
     public float getReorderBounceScale() {
         return mScaleForReorderBounce;
     }
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index ce47d70..80bc1a7 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
 import static com.android.launcher3.Utilities.ATLEAST_S;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_WIDGET_CENTERING;
 
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -314,7 +315,7 @@
                 setScaleToFit(1.0f);
             }
             // When the drag start, translations need to be set to zero to center the view
-            setTranslationForCentering(0f, 0f);
+            getTranslateDelegate().setTranslation(INDEX_WIDGET_CENTERING, 0f, 0f);
         }
     }
 
@@ -464,7 +465,8 @@
             } else {
                 mAppWidgetHostViewPreview.setScaleToFit(mAppWidgetHostViewScale);
             }
-            mAppWidgetHostViewPreview.setTranslationForCentering(
+            mAppWidgetHostViewPreview.getTranslateDelegate().setTranslation(
+                    INDEX_WIDGET_CENTERING,
                     -(params.width - (params.width * mPreviewContainerScale)) / 2.0f,
                     -(params.height - (params.height * mPreviewContainerScale)) / 2.0f);
             mWidgetImageContainer.addView(mAppWidgetHostViewPreview, /* index= */ 0);
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 77781bd..f4d6749 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -939,7 +939,7 @@
     protected boolean hasSeenEducationDialog() {
         return mActivityContext.getSharedPrefs()
                 .getBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, false)
-                || Utilities.IS_RUNNING_IN_TEST_HARNESS;
+                || Utilities.isRunningInTestHarness();
     }
 
     private void setUpEducationViewsIfNeeded() {
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/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index 82d9630..667290f 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -54,5 +54,14 @@
         return createMenuItem(menuItem);
     }
 
+    /**
+     * Returns a menu item that matches the text "Split screen". Fails if it doesn't exist.
+     */
+    public SplitScreenMenuItem getSplitScreenMenuItem() {
+        final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
+                AppIcon.getAppIconSelector("Split screen", mLauncher));
+        return new SplitScreenMenuItem(mLauncher, menuItem);
+    }
+
     protected abstract AppIconMenuItem createMenuItem(UiObject2 menuItem);
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 3dcb437..48e327f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -76,6 +76,27 @@
         }
     }
 
+    /**
+     * Clicks a launcher object to initiate splitscreen, where the selected app will be one of two
+     * apps running on the screen. Should be called when Launcher is in a "split staging" state
+     * and is waiting for the user's selection of a second app. Expects a SPLIT_START_EVENT to be
+     * fired when the click is executed.
+     */
+    public LaunchedAppState launchIntoSplitScreen() {
+        try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                "want to launch split tasks from " + launchableType())) {
+            LauncherInstrumentation.log("Launchable.launch before click "
+                    + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
+
+            mLauncher.clickLauncherObject(mObject);
+
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_START_EVENT);
+                return new LaunchedAppState(mLauncher);
+            }
+        }
+    }
+
     protected LaunchedAppState assertAppLaunched(BySelector selector) {
         mLauncher.assertTrue(
                 "App didn't start: (" + selector + ")",
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index adc993d..90f3d13 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -37,10 +37,9 @@
 public final class OverviewTask {
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
-    static final Pattern TASK_START_EVENT =
-            Pattern.compile("startActivityFromRecentsAsync");
-    static final Pattern SPLIT_START_EVENT =
-            Pattern.compile("launchSplitTasks");
+    static final Pattern TASK_START_EVENT = Pattern.compile("startActivityFromRecentsAsync");
+    static final Pattern SPLIT_SELECT_EVENT = Pattern.compile("enterSplitSelect");
+    static final Pattern SPLIT_START_EVENT = Pattern.compile("launchSplitTasks");
     private final LauncherInstrumentation mLauncher;
     private final UiObject2 mTask;
     private final BaseOverview mOverview;
diff --git a/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java b/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java
new file mode 100644
index 0000000..47cf20b
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SplitScreenMenuItem.java
@@ -0,0 +1,55 @@
+/*
+ * 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.shared.TestProtocol;
+
+/**
+ * A class representing the "Split screen" menu item in the app long-press menu. Used for TAPL
+ * testing in a similar way as other menu items {@link AppIconMenuItem}, but unlike AppIconMenuItem,
+ * the split screen command does not trigger an app launch. Instead, it causes Launcher to shift to
+ * a different state (OverviewSplitSelect).
+ */
+public final class SplitScreenMenuItem {
+    private final LauncherInstrumentation mLauncher;
+    private final UiObject2 mObject;
+
+    SplitScreenMenuItem(LauncherInstrumentation launcher, UiObject2 object) {
+        mLauncher = launcher;
+        mObject = object;
+    }
+
+    /**
+     * Executes a click command on this menu item. Expects a SPLIT_SELECT_EVENT to be fired.
+     */
+    public void click() {
+        try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                "want to enter split select from app long-press menu")) {
+            LauncherInstrumentation.log("clicking on split screen menu item "
+                    + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
+
+            mLauncher.clickLauncherObject(mObject);
+
+            try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_SELECT_EVENT);
+                mLauncher.waitForLauncherObject("split_placeholder");
+            }
+        }
+    }
+}