Merge "Sync overview scroll so that it ends before/at same time as taskbar animation" into tm-qpr-dev
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index a91507c..d581582 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -49,4 +49,6 @@
     <item name="config_wallpaperMaxScale" format="float" type="dimen">
         @*android:dimen/config_wallpaperMaxScale
     </item>
+
+    <string name="setup_wizard_pkg" translatable="false" />
 </resources>
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..ac2c44b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -198,15 +198,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() {
@@ -280,7 +272,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/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/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/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index ac92374..2f8cae8 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.getTranslationX(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.getTranslationX(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/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/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index e264a7f..e9385d9 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -878,7 +878,7 @@
         if (DesktopTaskView.DESKTOP_MODE_SUPPORTED && targets.hasDesktopTasks()) {
             mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
         } else {
-            mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets);
+            mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
         }
         mRecentsAnimationController = controller;
         mRecentsAnimationTargets = targets;
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 589459f..a8f3c3a 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -37,6 +37,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.R;
 import com.android.launcher3.tracing.OverviewComponentObserverProto;
 import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -65,6 +66,7 @@
     private final Intent mMyHomeIntent;
     private final Intent mFallbackIntent;
     private final SparseIntArray mConfigChangesMap = new SparseIntArray();
+    private final String mSetupWizardPkg;
 
     private Consumer<Boolean> mOverviewChangeListener = b -> { };
 
@@ -86,6 +88,7 @@
                 new ComponentName(context.getPackageName(), info.activityInfo.name);
         mMyHomeIntent.setComponent(myHomeComponent);
         mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
+        mSetupWizardPkg = context.getString(R.string.setup_wizard_pkg);
 
         ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
         mFallbackIntent = new Intent(Intent.ACTION_MAIN)
@@ -127,6 +130,12 @@
     private void updateOverviewTargets() {
         ComponentName defaultHome = PackageManagerWrapper.getInstance()
                 .getHomeActivities(new ArrayList<>());
+        if (defaultHome != null && defaultHome.getPackageName().equals(mSetupWizardPkg)) {
+            // Treat setup wizard as null default home, because there is a period between setup and
+            // launcher being default home where it is briefly null. Otherwise, it would appear as
+            // if overview targets are changing twice, giving the listener an incorrect signal.
+            defaultHome = null;
+        }
 
         mIsHomeDisabled = mDeviceState.isHomeDisabled();
         mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 9b00dcf..f30d3f1 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -89,7 +89,7 @@
      * Length of targets.apps should match that of {@link #mRemoteTargetHandles}.
      *
      * If split screen may be active when this is called, you might want to use
-     * {@link #assignTargetsForSplitScreen(Context, RemoteAnimationTargets)}
+     * {@link #assignTargetsForSplitScreen(RemoteAnimationTargets)}
      */
     public RemoteTargetHandle[] assignTargets(RemoteAnimationTargets targets) {
         for (int i = 0; i < mRemoteTargetHandles.length; i++) {
@@ -102,43 +102,45 @@
     }
 
     /**
-     * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this matches the
-     * apps in targets.apps to that of the _active_ split screened tasks.
-     * See {@link #assignTargetsForSplitScreen(RemoteAnimationTargets, int[])}
+     * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this assigns the
+     * apps in {@code targets.apps} to the {@link #mRemoteTargetHandles} with index 0 will being
+     * the left/top task, index 1 right/bottom.
      */
-    public RemoteTargetHandle[] assignTargetsForSplitScreen(
-            Context context, RemoteAnimationTargets targets) {
-        int[] splitIds = TopTaskTracker.INSTANCE.get(context).getRunningSplitTaskIds();
-        return assignTargetsForSplitScreen(targets, splitIds);
-    }
-
-    /**
-     * Assigns the provided splitIDs to the {@link #mRemoteTargetHandles}, with index 0 will being
-     * the left/top task, index 1 right/bottom
-     */
-    public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets,
-            int[] splitIds) {
-        RemoteAnimationTarget topLeftTarget; // only one set if single/fullscreen task
-        RemoteAnimationTarget bottomRightTarget;
+    public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
         if (mRemoteTargetHandles.length == 1) {
             // If we're not in split screen, the splitIds count doesn't really matter since we
             // should always hit this case.
             mRemoteTargetHandles[0].mTransformParams.setTargetSet(targets);
             if (targets.apps.length > 0) {
                 // Unclear why/when target.apps length == 0, but it sure does happen :(
-                topLeftTarget = targets.apps[0];
-                mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget, null);
+                mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(targets.apps[0], null);
             }
         } else {
-            // split screen
-            topLeftTarget = targets.findTask(splitIds[0]);
-            bottomRightTarget = targets.findTask(splitIds[1]);
+            RemoteAnimationTarget topLeftTarget = targets.apps[0];
+
+            // Fetch the adjacent target for split screen.
+            RemoteAnimationTarget bottomRightTarget = null;
+            for (int i = 1; i < targets.apps.length; i++) {
+                final RemoteAnimationTarget target = targets.apps[i];
+                Rect topLeftBounds = getStartBounds(topLeftTarget);
+                Rect bounds = getStartBounds(target);
+                if (topLeftBounds.left > bounds.right || topLeftBounds.top > bounds.bottom) {
+                    bottomRightTarget = topLeftTarget;
+                    topLeftTarget = target;
+                    break;
+                } else if (topLeftBounds.right < bounds.left || topLeftBounds.bottom < bounds.top) {
+                    bottomRightTarget = target;
+                    break;
+                }
+            }
 
             // remoteTargetHandle[0] denotes topLeft task, so we pass in the bottomRight to exclude,
             // vice versa
             mSplitBounds = new SplitBounds(
                     getStartBounds(topLeftTarget),
-                    getStartBounds(bottomRightTarget), splitIds[0], splitIds[1]);
+                    getStartBounds(bottomRightTarget),
+                    topLeftTarget.taskId,
+                    bottomRightTarget.taskId);
             mRemoteTargetHandles[0].mTransformParams.setTargetSet(
                     createRemoteAnimationTargetsForTarget(targets, bottomRightTarget));
             mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(topLeftTarget,
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 1a72e3f..da97df6 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -190,7 +190,7 @@
             if (forDesktop) {
                 remoteTargetHandles = gluer.assignTargetsForDesktop(targets);
             } else if (v.containsMultipleTasks()) {
-                remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets, v.getTaskIds());
+                remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets);
             } else {
                 remoteTargetHandles = gluer.assignTargets(targets);
             }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index accab38..4680608 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -1132,6 +1132,10 @@
             return;
         }
 
+        // TODO(b/258022658): Remove temporary logging.
+        Log.i(TAG, "preloadOverview: forSUWAllSet=" + forSUWAllSet
+                + ", isHomeAndOverviewSame=" + mOverviewComponentObserver.isHomeAndOverviewSame());
+
         mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
     }
 
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/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 89177b6..14898b1 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -381,6 +381,7 @@
         }
         setOverlayEnabled(false);
         onTaskListVisibilityChanged(false);
+        setVisibility(VISIBLE);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 0284d29..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;
@@ -706,6 +708,12 @@
     private ObjectAnimator mActionsViewAlphaAnimator;
     private float mActionsViewAlphaAnimatorFinalValue;
 
+    /**
+     * Keeps track of the desktop task. Optional and only present when the feature flag is enabled.
+     */
+    @Nullable
+    private DesktopTaskView mDesktopTaskView;
+
     private MultiWindowModeChangedListener mMultiWindowModeChangedListener =
             new MultiWindowModeChangedListener() {
                 @Override
@@ -1586,6 +1594,8 @@
         // update the map of instance counts
         mFilterState.updateInstanceCountMap(taskGroups);
 
+        // Clear out desktop view if it is set
+        mDesktopTaskView = null;
         DesktopTask desktopTask = null;
 
         // Add views as children based on whether it's grouped or single task. Looping through
@@ -1644,12 +1654,13 @@
         if (!taskGroups.isEmpty()) {
             addView(mClearAllButton);
 
-            if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
-                TaskView taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
+            if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED
+                    && !getSplitSelectController().isSplitSelectActive()) {
+                mDesktopTaskView = (DesktopTaskView) getTaskViewFromPool(TaskView.Type.DESKTOP);
                 // Always add a desktop task to the first position. Even if it is empty
-                addView(taskView, 0);
+                addView(mDesktopTaskView, 0);
                 ArrayList<Task> tasks = desktopTask != null ? desktopTask.tasks : new ArrayList<>();
-                ((DesktopTaskView) taskView).bind(tasks, mOrientationState);
+                mDesktopTaskView.bind(tasks, mOrientationState);
             }
         }
 
@@ -1709,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
@@ -2774,6 +2785,10 @@
             } else if (taskView.isDesktopTask()) {
                 // Desktop task was not focused. Pin it to the right of focused
                 desktopTaskIndex = i;
+                if (taskView.getVisibility() == View.GONE) {
+                    // Desktop task view is hidden, skip it from grid calculations
+                    continue;
+                }
                 if (!ENABLE_GRID_ONLY_OVERVIEW.get()) {
                     // Only apply x-translation when using legacy overview grid
                     gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
@@ -4467,6 +4482,9 @@
         mSplitSelectStateController.setAnimateCurrentTaskDismissal(
                 true /*animateCurrentTaskDismissal*/);
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
+        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+            updateDesktopTaskVisibility(false /* visible */);
+        }
     }
 
     /**
@@ -4475,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);
@@ -4483,6 +4502,15 @@
         mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
                 splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
                 splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
+        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+            updateDesktopTaskVisibility(false /* visible */);
+        }
+    }
+
+    private void updateDesktopTaskVisibility(boolean visible) {
+        if (mDesktopTaskView != null) {
+            mDesktopTaskView.setVisibility(visible ? VISIBLE : GONE);
+        }
     }
 
     /**
@@ -4633,6 +4661,9 @@
             mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE);
             mSplitHiddenTaskView = null;
         }
+        if (DesktopTaskView.DESKTOP_IS_PROTO2_ENABLED) {
+            updateDesktopTaskVisibility(true /* visible */);
+        }
     }
 
     private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -5004,8 +5035,7 @@
             mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets);
         } else {
             gluer = new RemoteTargetGluer(getContext(), getSizeStrategy());
-            mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(
-                    getContext(), recentsAnimationTargets);
+            mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets);
         }
         mSplitBoundsConfig = gluer.getSplitBounds();
         // Add release check to the targets from the RemoteTargetGluer and not the targets
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/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/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..731ad74 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;
@@ -70,6 +72,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;
@@ -1099,13 +1102,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 +1125,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 +1136,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)) {
@@ -1434,7 +1437,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 +1459,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 +1480,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 +1491,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 +1576,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);
         }
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/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/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/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index c98b60f..98b61d1 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -21,9 +21,14 @@
 
 import android.content.Context;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.Utilities;
 
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
+
 /**
  * Defines a set of flags used to control various launcher behaviors.
  *
@@ -33,6 +38,11 @@
 
     public static final String FLAGS_PREF_NAME = "featureFlags";
 
+    @VisibleForTesting
+    public static Predicate<BooleanFlag> sBooleanReader = f -> f.mCurrentValue;
+    @VisibleForTesting
+    public static ToIntFunction<IntFlag> sIntReader = f -> f.mCurrentValue;
+
     private FeatureFlags() { }
 
     public static boolean showFlagTogglerUi(Context context) {
@@ -385,7 +395,7 @@
         }
 
         public boolean get() {
-            return mCurrentValue;
+            return sBooleanReader.test(this);
         }
     }
 
@@ -401,7 +411,7 @@
         }
 
         public int get() {
-            return mCurrentValue;
+            return sIntReader.applyAsInt(this);
         }
     }
 }
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/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/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java
index d7c6c4f..433fd31 100644
--- a/tests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/src/com/android/launcher3/util/TestUtil.java
@@ -27,12 +27,18 @@
 
 import androidx.test.uiautomator.UiDevice;
 
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.config.FeatureFlags.BooleanFlag;
+import com.android.launcher3.config.FeatureFlags.IntFlag;
+
 import org.junit.Assert;
 
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
+import java.util.function.ToIntFunction;
 
 public class TestUtil {
     public static final String DUMMY_PACKAGE = "com.example.android.aardwolf";
@@ -68,6 +74,36 @@
         }
     }
 
+    /**
+     * Utility class to override a boolean flag during test. Note that the returned SafeCloseable
+     * must be closed to restore the original state
+     */
+    public static SafeCloseable overrideFlag(BooleanFlag flag, boolean value) {
+        Predicate<BooleanFlag> originalProxy = FeatureFlags.sBooleanReader;
+        Predicate<BooleanFlag> testProxy = f -> f == flag ? value : originalProxy.test(f);
+        FeatureFlags.sBooleanReader = testProxy;
+        return () -> {
+            if (FeatureFlags.sBooleanReader == testProxy) {
+                FeatureFlags.sBooleanReader = originalProxy;
+            }
+        };
+    }
+
+    /**
+     * Utility class to override a int flag during test. Note that the returned SafeCloseable
+     * must be closed to restore the original state
+     */
+    public static SafeCloseable overrideFlag(IntFlag flag, int value) {
+        ToIntFunction<IntFlag> originalProxy = FeatureFlags.sIntReader;
+        ToIntFunction<IntFlag> testProxy = f -> f == flag ? value : originalProxy.applyAsInt(f);
+        FeatureFlags.sIntReader = testProxy;
+        return () -> {
+            if (FeatureFlags.sIntReader == testProxy) {
+                FeatureFlags.sIntReader = originalProxy;
+            }
+        };
+    }
+
     public static void uninstallDummyApp() throws IOException {
         UiDevice.getInstance(getInstrumentation()).executeShellCommand(
                 "pm uninstall " + DUMMY_PACKAGE);
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");
+            }
+        }
+    }
+}