Merge "Moving startBinding and clearPendingBinds to ModelCallbacks" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 760d8ac..9080284 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -76,3 +76,10 @@
     description: "Enables logging of Launcher restore metrics to the Backup & Restore team"
     bug: "307527314"
 }
+
+flag {
+    name: "enable_unfolded_two_pane_picker"
+    namespace: "launcher"
+    description: "Enables two pane widget picker for unfolded foldables"
+    bug: "313922374"
+}
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
index 5a9e147..8240f11 100644
--- a/protos/launcher_atom.proto
+++ b/protos/launcher_atom.proto
@@ -138,7 +138,7 @@
   }
 }
 
-// Next value 54
+// Next value 55
 enum Attribute {
   option allow_alias = true;
 
@@ -200,6 +200,7 @@
   DATA_SOURCE_APPSEARCH_CATEGORY_SRP_PREVIEW = 48;
   DATA_SOURCE_APPSEARCH_ENTITY_SRP_PREVIEW = 49;
   DATA_SOURCE_AIAI_SEARCH_ROOT = 47;
+  DATA_SOURCE_LAUNCHER = 54;
 
   // Web suggestions provided by AGA
   ALL_APPS_SEARCH_RESULT_WEB_SUGGEST = 39;
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index d6ab54e..8db63e3 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -55,6 +55,7 @@
 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
+import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
 import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
 import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
@@ -120,6 +121,7 @@
 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.model.data.ItemInfo;
@@ -1651,6 +1653,15 @@
         if (launcherIsForceInvisibleOrOpening) {
             addCujInstrumentation(anim, playFallBackAnimation
                     ? CUJ_APP_CLOSE_TO_HOME_FALLBACK : CUJ_APP_CLOSE_TO_HOME);
+
+            anim.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    AccessibilityManagerCompat.sendTestProtocolEventToTest(
+                            mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE);
+                }
+            });
+
             // Only register the content animation for cancellation when state changes
             mLauncher.getStateManager().setCurrentAnimation(anim);
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index cbb991d..b29ce6b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -133,11 +133,20 @@
         GroupTask task = mControllerCallbacks.getTaskAt(index);
         if (task == null) {
             return Math.max(0, index);
-        } else if (mOnDesktop) {
+        }
+        Task task2 = task.task2;
+        int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
+        if (runningTaskId == task.task1.key.id
+                || (task2 != null && runningTaskId == task2.key.id)) {
+            // Ignore attempts to run the selected task if it is already running.
+            return -1;
+        }
+
+        if (mOnDesktop) {
             UI_HELPER_EXECUTOR.execute(() ->
                     SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
                             .showDesktopApp(task.task1.key.id));
-        } else if (task.task2 == null) {
+        } else if (task2 == null) {
             UI_HELPER_EXECUTOR.execute(() ->
                     ActivityManagerWrapper.getInstance().startActivityFromRecents(
                             task.task1.key,
@@ -145,8 +154,7 @@
                                     taskView == null ? mKeyboardQuickSwitchView : taskView, null)
                                     .options));
         } else {
-            mControllers.uiController.launchSplitTasks(
-                    taskView == null ? mKeyboardQuickSwitchView : taskView, task);
+            mControllers.uiController.launchSplitTasks(task);
         }
         return -1;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index bbe73ff..b4754c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -26,7 +26,6 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.TaskTransitionSpec;
-import android.view.View;
 import android.view.WindowManagerGlobal;
 
 import androidx.annotation.NonNull;
@@ -386,8 +385,8 @@
     }
 
     @Override
-    public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
-        mLauncher.launchSplitTasks(taskView, groupTask);
+    public void launchSplitTasks(@NonNull GroupTask groupTask) {
+        mLauncher.launchSplitTasks(groupTask);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 988ef80..60ee38f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -203,11 +203,16 @@
         Display display = windowContext.getDisplay();
         Context c = getApplicationContext();
         mWindowManager = c.getSystemService(WindowManager.class);
-        mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
-        mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
+
+        boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
+        mLeftCorner = phoneMode
+                ? null
+                : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+        mRightCorner = phoneMode
+                ? null
+                : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
 
         // Inflate views.
-        boolean phoneMode = TaskbarManager.isPhoneMode(mDeviceProfile);
         int taskbarLayout = DisplayController.isTransientTaskbar(this) && !phoneMode
                 ? R.layout.transient_taskbar
                 : R.layout.taskbar;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 445b312..aee3c6f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -297,11 +297,9 @@
     }
 
     /**
-     * Launches the focused task in splitscreen.
-     *
-     * No-op if the view is not yet open.
+     * Launches the given task in split-screen.
      */
-    public void launchSplitTasks(@NonNull View taskview, @NonNull GroupTask groupTask) { }
+    public void launchSplitTasks(@NonNull GroupTask groupTask) { }
 
     /**
      * Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 5b0c8c3..89b7fa4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -19,6 +19,7 @@
 import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
+
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -34,8 +35,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_HOME_TRANSITION_LISTENER;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
 import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
@@ -1263,24 +1262,19 @@
 
     /**
      * Launches the given {@link GroupTask} in splitscreen.
-     *
-     * If the second split task is missing, launches the first task normally.
      */
-    public void launchSplitTasks(@NonNull View taskView, @NonNull GroupTask groupTask) {
-        if (groupTask.task2 == null) {
-            UI_HELPER_EXECUTOR.execute(() ->
-                    ActivityManagerWrapper.getInstance().startActivityFromRecents(
-                            groupTask.task1.key,
-                            getActivityLaunchOptions(taskView, null).options));
-            return;
-        }
+    public void launchSplitTasks(@NonNull GroupTask groupTask) {
+        // Top/left and bottom/right tasks respectively.
+        Task task1 = groupTask.task1;
+        // task2 should never be null when calling this method. Allow a crash to catch invalid calls
+        Task task2 = groupTask.task2;
         mSplitSelectStateController.launchExistingSplitPair(
                 null /* launchingTaskView */,
-                groupTask.task1.key.id,
-                groupTask.task2.key.id,
+                task1.key.id,
+                task2.key.id,
                 SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
                 /* callback= */ success -> mSplitSelectStateController.resetState(),
-                /* freezeTaskList= */ true,
+                /* freezeTaskList= */ false,
                 groupTask.mSplitBounds == null
                         ? SNAP_TO_50_50
                         : groupTask.mSplitBounds.snapPosition);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
index 6671fc3..301fbe4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
@@ -359,7 +359,7 @@
             category.addPreference(createSeekBarPreference("Haptic hint scale exponent",
                     1, 5, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT));
             category.addPreference(createSeekBarPreference("Haptic hint iterations (12 ms each)",
-                    0, 100, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS));
+                    0, 200, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS));
             category.addPreference(createSeekBarPreference("Haptic hint delay (ms)",
                     0, 400, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY));
         }
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 5568459..9e58160 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -416,6 +417,10 @@
         if (mLauncher.isDestroyed()) {
             return;
         }
+        LauncherTaskbarUIController taskbarUIController = mLauncher.getTaskbarUIController();
+        if (taskbarUIController != null) {
+            taskbarUIController.onLauncherVisibilityChanged(true);
+        }
         // TODO: Catch the moment when launcher becomes visible after the top app un-occludes
         //  launcher and start animating afterwards. Currently we occasionally get a flicker from
         //  animating when launcher is still invisible.
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 39edd70..22163b9 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -18,9 +18,11 @@
 import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
+import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE;
 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
@@ -344,6 +346,8 @@
         // Workaround for b/78520668, explicitly trim memory once UI is hidden
         onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
         mFallbackRecentsView.updateLocusId();
+        AccessibilityManagerCompat.sendTestProtocolEventToTest(
+                this, LAUNCHER_ACTIVITY_STOPPED_MESSAGE);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 0d7ca07..27de20c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -403,10 +403,10 @@
     }
 
     @Override
-    public void animateNavBarLongPress(boolean isTouchDown, long durationMs) {
+    public void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {
         if (mSystemUiProxy != null) {
             try {
-                mSystemUiProxy.animateNavBarLongPress(isTouchDown, durationMs);
+                mSystemUiProxy.animateNavBarLongPress(isTouchDown, shrink, durationMs);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call animateNavBarLongPress", e);
             }
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 20a751b..1b3f598 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -31,7 +31,6 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.SparseArray;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.WorkerThread;
 
@@ -45,6 +44,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.Preconditions;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.TaskKeyLruCache;
@@ -62,7 +62,6 @@
 public class TaskIconCache implements DisplayInfoChangeListener {
 
     private final Executor mBgExecutor;
-    private final AccessibilityManager mAccessibilityManager;
 
     private final Context mContext;
     private final TaskKeyLruCache<TaskCacheEntry> mIconCache;
@@ -79,7 +78,6 @@
     public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) {
         mContext = context;
         mBgExecutor = bgExecutor;
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mIconProvider = iconProvider;
 
         Resources res = context.getResources();
@@ -238,14 +236,11 @@
             if ((index = mDefaultIcons.indexOfKey(userId)) >= 0) {
                 return mDefaultIcons.valueAt(index).newIcon(mContext);
             } else {
-                try (BaseIconFactory li = getIconFactory()) {
-                    BitmapInfo info = mDefaultIconBase.withFlags(
-                            li.getBitmapFlagOp(new IconOptions()
-                                    .setUser(UserCache.INSTANCE.get(mContext)
-                                            .getUserInfo(UserHandle.of(userId)))));
-                    mDefaultIcons.put(userId, info);
-                    return info.newIcon(mContext);
-                }
+                BitmapInfo info = mDefaultIconBase.withFlags(
+                        UserCache.INSTANCE.get(mContext).getUserInfo(UserHandle.of(userId))
+                                .applyBitmapInfoFlags(FlagOp.NO_OP));
+                mDefaultIcons.put(userId, info);
+                return info.newIcon(mContext);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index e5fca4b..9e21595 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -195,11 +195,15 @@
             return null;
         }
 
-        CancellableTask<ThumbnailData> request = new CancellableTask<ThumbnailData>() {
+        CancellableTask<ThumbnailData> request = new CancellableTask<>() {
             @Override
             public ThumbnailData getResultOnBg() {
-                return ActivityManagerWrapper.getInstance().getTaskThumbnail(
+                ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail(
                         key.id, lowResolution);
+                if (thumbnailData.thumbnail != null) {
+                    return thumbnailData;
+                }
+                return ActivityManagerWrapper.getInstance().takeTaskThumbnail(key.id);
             }
 
             @Override
diff --git a/quickstep/src/com/android/quickstep/util/AssistStateManager.java b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
index a0bb76d..f7437eb 100644
--- a/quickstep/src/com/android/quickstep/util/AssistStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/AssistStateManager.java
@@ -31,6 +31,11 @@
 
     public AssistStateManager() {}
 
+    /** Whether search supports haptic on invocation. */
+    public boolean supportsCommitHaptic() {
+        return false;
+    }
+
     /** Whether search is available. */
     public boolean isSearchAvailable() {
         return false;
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 1effaff..dfbd32c 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -257,10 +257,6 @@
                 Interpolators.clampToProgress(Interpolators.LINEAR,
                         timings.instructionsContainerFadeInStartOffset,
                         timings.instructionsContainerFadeInEndOffset))
-        anim.setViewAlpha(splitInstructionsView!!.textView, 1f,
-                Interpolators.clampToProgress(Interpolators.LINEAR,
-                        timings.instructionsTextFadeInStartOffset,
-                        timings.instructionsTextFadeInEndOffset))
         anim.addFloat(splitInstructionsView, SplitInstructionsView.UNFOLD, 0.1f, 1f,
                 Interpolators.clampToProgress(Interpolators.EMPHASIZED_DECELERATE,
                         timings.instructionsUnfoldStartOffset,
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index 423ba43..c013483 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -93,6 +93,7 @@
     private var secondTaskId: Int = INVALID_TASK_ID
     private var initialIntent: Intent? = null
     private var secondIntent: Intent? = null
+    private var widgetSecondIntent: Intent? = null
     private var initialUser: UserHandle? = null
     private var secondUser: UserHandle? = null
     private var initialPendingIntent: PendingIntent? = null
@@ -167,6 +168,16 @@
         secondUser = pendingIntent.creatorUserHandle
     }
 
+    /**
+     * Similar to [setSecondTask] except this is to be called for widgets which can pass through
+     * an extra intent from their RemoteResponse.
+     * See [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
+     */
+    fun setSecondWidget(pendingIntent: PendingIntent, widgetIntent: Intent?) {
+        setSecondTask(pendingIntent)
+        widgetSecondIntent = widgetIntent
+    }
+
     private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? {
         val intentPackage = intent?.getPackage() ?: return null
         val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
@@ -241,6 +252,7 @@
                 secondTaskId,
                 initialPendingIntent,
                 secondPendingIntent,
+                widgetSecondIntent,
                 initialUser?.identifier ?: -1,
                 secondUser?.identifier ?: -1,
                 initialShortcut,
@@ -257,7 +269,8 @@
      * Note that both [initialIntent] and [secondIntent] will be nullified on method return
      *
      * One caveat is that if [secondPendingIntent] is set, we will use that and *not* attempt to
-     * convert [secondIntent]
+     * convert [secondIntent].
+     * This also leaves [widgetSecondIntent] untouched.
      */
     private fun convertIntentsToFinalTypes() {
         initialShortcut = getShortcutInfo(initialIntent, initialUser)
@@ -343,6 +356,7 @@
             var secondTaskId: Int = INVALID_TASK_ID,
             var initialPendingIntent: PendingIntent? = null,
             var secondPendingIntent: PendingIntent? = null,
+            var widgetSecondIntent: Intent? = null,
             var initialUserId: Int = -1,
             var secondUserId: Int = -1,
             var initialShortcut: ShortcutInfo? = null,
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 145707b..8b27a85 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -32,6 +32,7 @@
 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
 import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
+import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
 
 import android.animation.Animator;
@@ -355,6 +356,10 @@
         mSplitSelectDataHolder.setSecondTask(pendingIntent);
     }
 
+    public void setSecondWidget(PendingIntent pendingIntent, Intent widgetIntent) {
+        mSplitSelectDataHolder.setSecondWidget(pendingIntent, widgetIntent);
+    }
+
     /**
      * To be called when we want to launch split pairs from Overview. Split can be initiated from
      * either Overview or home, or all apps. Either both taskIds are set, or a pending intent + a
@@ -380,11 +385,13 @@
         ShortcutInfo secondShortcut = launchData.getSecondShortcut();
         PendingIntent firstPI = launchData.getInitialPendingIntent();
         PendingIntent secondPI = launchData.getSecondPendingIntent();
+        Intent widgetIntent = launchData.getWidgetSecondIntent();
         int firstUserId = launchData.getInitialUserId();
         int secondUserId = launchData.getSecondUserId();
         int initialStagePosition = launchData.getInitialStagePosition();
         Bundle optionsBundle = options1.toBundle();
-
+        Bundle extrasBundle = new Bundle(1);
+        extrasBundle.putParcelable(KEY_EXTRA_WIDGET_INTENT, widgetIntent);
         if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
             final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
                     secondTaskId, callback, "LaunchSplitPair");
@@ -396,7 +403,7 @@
 
                 case SPLIT_TASK_PENDINGINTENT ->
                         mSystemUiProxy.startIntentAndTask(secondPI, secondUserId, optionsBundle,
-                                firstTaskId, null /*options2*/, initialStagePosition, snapPosition,
+                                firstTaskId, extrasBundle, initialStagePosition, snapPosition,
                                 remoteTransition, shellInstanceId);
 
                 case SPLIT_TASK_SHORTCUT ->
@@ -411,9 +418,9 @@
 
                 case SPLIT_PENDINGINTENT_PENDINGINTENT ->
                         mSystemUiProxy.startIntents(firstPI, firstUserId, firstShortcut,
-                                optionsBundle, secondPI, secondUserId, secondShortcut,
-                                null /*options2*/, initialStagePosition, snapPosition,
-                                remoteTransition, shellInstanceId);
+                                optionsBundle, secondPI, secondUserId, secondShortcut, extrasBundle,
+                                initialStagePosition, snapPosition, remoteTransition,
+                                shellInstanceId);
 
                 case SPLIT_SHORTCUT_TASK ->
                         mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 9313342..e705285 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -72,7 +72,8 @@
      * @return {@code true} if we can attempt launch the widget into split, {@code false} otherwise
      *         to allow launcher to handle the click
      */
-    public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent) {
+    public boolean handleSecondWidgetSelectionForSplit(View view, PendingIntent pendingIntent,
+            Intent remoteResponseIntent) {
         if (shouldIgnoreSecondSplitLaunch()) {
             return false;
         }
@@ -86,7 +87,7 @@
             Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 
             view.post(() -> {
-                mController.setSecondTask(pendingIntent);
+                mController.setSecondWidget(pendingIntent, remoteResponseIntent);
                 // Convert original widgetView into bitmap to use for animation
                 Canvas canvas = new Canvas(bitmap);
                 view.draw(canvas);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6235490..7e1034b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3298,9 +3298,6 @@
         anim.setViewAlpha(splitInstructionsView, 1, clampToProgress(LINEAR,
                 timings.getInstructionsContainerFadeInStartOffset(),
                 timings.getInstructionsContainerFadeInEndOffset()));
-        anim.setViewAlpha(splitInstructionsView.getTextView(), 1, clampToProgress(LINEAR,
-                timings.getInstructionsTextFadeInStartOffset(),
-                timings.getInstructionsTextFadeInEndOffset()));
         anim.addFloat(splitInstructionsView, splitInstructionsView.UNFOLD, 0.1f, 1,
                 clampToProgress(EMPHASIZED_DECELERATE,
                         timings.getInstructionsUnfoldStartOffset(),
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index 8bc85cf..f6501f1 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -23,9 +23,9 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.annotation.Nullable;
-import androidx.appcompat.widget.AppCompatTextView;
 
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
@@ -41,12 +41,9 @@
  */
 public class SplitInstructionsView extends LinearLayout {
     private final StatefulActivity mLauncher;
-    private AppCompatTextView mInstructionTextView;
-    /** Only used if {@link com.android.wm.shell.FeatureFlags#enableSplitContextual()} is true. */
-    private AppCompatTextView mCancelTextView;
 
     public static final FloatProperty<SplitInstructionsView> UNFOLD =
-            new FloatProperty<SplitInstructionsView>("SplitInstructionsUnfold") {
+            new FloatProperty<>("SplitInstructionsUnfold") {
                 @Override
                 public void setValue(SplitInstructionsView splitInstructionsView, float v) {
                     splitInstructionsView.setScaleY(v);
@@ -97,12 +94,11 @@
     }
 
     private void init() {
-        mInstructionTextView = findViewById(R.id.split_instructions_text);
-        mCancelTextView = findViewById(R.id.split_instructions_text_cancel);
+        TextView cancelTextView = findViewById(R.id.split_instructions_text_cancel);
 
         if (FeatureFlags.enableSplitContextually()) {
-            mCancelTextView.setVisibility(VISIBLE);
-            mCancelTextView.setOnClickListener((v) -> exitSplitSelection());
+            cancelTextView.setVisibility(VISIBLE);
+            cancelTextView.setOnClickListener((v) -> exitSplitSelection());
         }
     }
 
@@ -147,8 +143,4 @@
                         getMeasuredWidth()
                 );
     }
-
-    public AppCompatTextView getTextView() {
-        return mInstructionTextView;
-    }
 }
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 137ffe8..025003b 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -432,7 +432,7 @@
     <dimen name="split_instructions_start_margin_cancel">8dp</dimen>
 
     <!-- Workspace grid visualization parameters -->
-    <dimen name="grid_visualization_rounding_radius">28dp</dimen>
+    <dimen name="grid_visualization_rounding_radius">16dp</dimen>
     <dimen name="grid_visualization_horizontal_cell_spacing">6dp</dimen>
     <dimen name="grid_visualization_vertical_cell_spacing">6dp</dimen>
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 11b263d..5443ff9 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -70,6 +70,7 @@
 import com.android.launcher3.celllayout.DelegatedCellDrawing;
 import com.android.launcher3.celllayout.ItemConfiguration;
 import com.android.launcher3.celllayout.ReorderAlgorithm;
+import com.android.launcher3.celllayout.ReorderParameters;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.PreviewBackground;
@@ -1741,140 +1742,6 @@
         return swapSolution.isSolution;
     }
 
-    /**
-     * Find a vacant area that will fit the given bounds nearest the requested
-     * cell location, and will also weigh in a suggested direction vector of the
-     * desired location. This method computers distance based on unit grid distances,
-     * not pixel distances.
-     *
-     * @param cellX The X cell nearest to which you want to search for a vacant area.
-     * @param cellY The Y cell nearest which you want to search for a vacant area.
-     * @param spanX Horizontal span of the object.
-     * @param spanY Vertical span of the object.
-     * @param direction The favored direction in which the views should move from x, y
-     * @param occupied The array which represents which cells in the CellLayout are occupied
-     * @param blockOccupied The array which represents which cells in the specified block (cellX,
-     *        cellY, spanX, spanY) are occupied. This is used when try to move a group of views.
-     * @param result Array in which to place the result, or null (in which case a new array will
-     *        be allocated)
-     * @return The X, Y cell of a vacant area that can contain this object,
-     *         nearest the requested location.
-     */
-    public int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
-            boolean[][] occupied, boolean blockOccupied[][], int[] result) {
-        // Keep track of best-scoring drop area
-        final int[] bestXY = result != null ? result : new int[2];
-        float bestDistance = Float.MAX_VALUE;
-        int bestDirectionScore = Integer.MIN_VALUE;
-
-        final int countX = mCountX;
-        final int countY = mCountY;
-
-        for (int y = 0; y < countY - (spanY - 1); y++) {
-            inner:
-            for (int x = 0; x < countX - (spanX - 1); x++) {
-                // First, let's see if this thing fits anywhere
-                for (int i = 0; i < spanX; i++) {
-                    for (int j = 0; j < spanY; j++) {
-                        if (occupied[x + i][y + j] && (blockOccupied == null || blockOccupied[i][j])) {
-                            continue inner;
-                        }
-                    }
-                }
-
-                float distance = (float) Math.hypot(x - cellX, y - cellY);
-                int[] curDirection = mTmpPoint;
-                computeDirectionVector(x - cellX, y - cellY, curDirection);
-                // The direction score is just the dot product of the two candidate direction
-                // and that passed in.
-                int curDirectionScore = direction[0] * curDirection[0] +
-                        direction[1] * curDirection[1];
-                if (Float.compare(distance,  bestDistance) < 0 ||
-                        (Float.compare(distance, bestDistance) == 0
-                                && curDirectionScore > bestDirectionScore)) {
-                    bestDistance = distance;
-                    bestDirectionScore = curDirectionScore;
-                    bestXY[0] = x;
-                    bestXY[1] = y;
-                }
-            }
-        }
-
-        // Return -1, -1 if no suitable location found
-        if (bestDistance == Float.MAX_VALUE) {
-            bestXY[0] = -1;
-            bestXY[1] = -1;
-        }
-        return bestXY;
-    }
-
-    /*
-     * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
-     * the provided point and the provided cell
-     */
-    private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
-        double angle = Math.atan(deltaY / deltaX);
-
-        result[0] = 0;
-        result[1] = 0;
-        if (Math.abs(Math.cos(angle)) > 0.5f) {
-            result[0] = (int) Math.signum(deltaX);
-        }
-        if (Math.abs(Math.sin(angle)) > 0.5f) {
-            result[1] = (int) Math.signum(deltaY);
-        }
-    }
-
-    /* This seems like it should be obvious and straight-forward, but when the direction vector
-    needs to match with the notion of the dragView pushing other views, we have to employ
-    a slightly more subtle notion of the direction vector. The question is what two points is
-    the vector between? The center of the dragView and its desired destination? Not quite, as
-    this doesn't necessarily coincide with the interaction of the dragView and items occupying
-    those cells. Instead we use some heuristics to often lock the vector to up, down, left
-    or right, which helps make pushing feel right.
-    */
-    public void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
-            int spanY, View dragView, int[] resultDirection) {
-
-        //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
-        int[] targetDestination = new int[2];
-
-        findNearestAreaIgnoreOccupied(dragViewCenterX, dragViewCenterY, spanX, spanY,
-                targetDestination);
-        Rect dragRect = new Rect();
-        cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
-        dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
-
-        Rect region = new Rect(targetDestination[0], targetDestination[1],
-                targetDestination[0] + spanX, targetDestination[1] + spanY);
-        Rect dropRegionRect = getIntersectingRectanglesInRegion(region, dragView);
-        if (dropRegionRect == null) dropRegionRect = new Rect(region);
-
-        int dropRegionSpanX = dropRegionRect.width();
-        int dropRegionSpanY = dropRegionRect.height();
-
-        cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
-                dropRegionRect.height(), dropRegionRect);
-
-        int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
-        int deltaY = (dropRegionRect.centerY() - dragViewCenterY) / spanY;
-
-        if (dropRegionSpanX == mCountX || spanX == mCountX) {
-            deltaX = 0;
-        }
-        if (dropRegionSpanY == mCountY || spanY == mCountY) {
-            deltaY = 0;
-        }
-
-        if (deltaX == 0 && deltaY == 0) {
-            // No idea what to do, give a random direction.
-            resultDirection[0] = 1;
-            resultDirection[1] = 0;
-        } else {
-            computeDirectionVector(deltaX, deltaY, resultDirection);
-        }
-    }
-
     public ReorderAlgorithm createReorderAlgorithm() {
         return new ReorderAlgorithm(this);
     }
@@ -1882,8 +1749,11 @@
     protected ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
             int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
             ItemConfiguration solution) {
-        return createReorderAlgorithm().findReorderSolution(pixelX, pixelY, minSpanX, minSpanY,
-                spanX, spanY, direction, dragView, decX, solution);
+        ItemConfiguration configuration = new ItemConfiguration();
+        copyCurrentStateToSolution(configuration);
+        ReorderParameters parameters = new ReorderParameters(pixelX, pixelY, spanX, spanY, minSpanX,
+                minSpanY, dragView, configuration);
+        return createReorderAlgorithm().findReorderSolution(parameters, decX);
     }
 
     public void copyCurrentStateToSolution(ItemConfiguration solution) {
@@ -1913,8 +1783,12 @@
      */
     public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX, int minSpanY,
             int spanX, int spanY, View dragView) {
-        return createReorderAlgorithm().calculateReorder(pixelX, pixelY, minSpanX, minSpanY,
-                spanX, spanY, dragView);
+        ItemConfiguration configuration = new ItemConfiguration();
+        copyCurrentStateToSolution(configuration);
+        return createReorderAlgorithm().calculateReorder(
+                new ReorderParameters(pixelX, pixelY, spanX, spanY,  minSpanX, minSpanY, dragView,
+                        configuration)
+        );
     }
 
     int[] performReorder(int pixelX, int pixelY, int minSpanX, int minSpanY, int spanX, int spanY,
diff --git a/src/com/android/launcher3/MultipageCellLayout.java b/src/com/android/launcher3/MultipageCellLayout.java
index 5c5d71c..0ec9034 100644
--- a/src/com/android/launcher3/MultipageCellLayout.java
+++ b/src/com/android/launcher3/MultipageCellLayout.java
@@ -56,17 +56,6 @@
     }
 
     @Override
-    public void getDirectionVectorForDrop(int dragViewCenterX, int dragViewCenterY, int spanX,
-            int spanY, View dragView, int[] resultDirection) {
-        createReorderAlgorithm().simulateSeam(
-                () -> {
-                    super.getDirectionVectorForDrop(dragViewCenterX, dragViewCenterY, spanX, spanY,
-                            dragView, resultDirection);
-                    return 0;
-                });
-    }
-
-    @Override
     public boolean isNearestDropLocationOccupied(int pixelX, int pixelY, int spanX, int spanY,
             View dragView, int[] result) {
         return createReorderAlgorithm().simulateSeam(
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index b74699a..e0f6101 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -79,7 +79,6 @@
 
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.TintedDrawableSpan;
-import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.ShortcutCachingLogic;
@@ -91,6 +90,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.Themes;
@@ -676,12 +676,11 @@
         }
 
         if (badge == null) {
-            try (LauncherIcons li = LauncherIcons.obtain(context)) {
-                badge = BitmapInfo.LOW_RES_INFO.withFlags(
-                                li.getBitmapFlagOp(new BaseIconFactory.IconOptions().setUser(
-                                        UserCache.INSTANCE.get(context).getUserInfo(info.user))))
-                        .getBadgeDrawable(context, useTheme);
-            }
+            badge = BitmapInfo.LOW_RES_INFO.withFlags(
+                            UserCache.INSTANCE.get(context)
+                                    .getUserInfo(info.user)
+                                    .applyBitmapInfoFlags(FlagOp.NO_OP))
+                    .getBadgeDrawable(context, useTheme);
             if (badge == null) {
                 badge = new ColorDrawable(Color.TRANSPARENT);
             }
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index d8f6689..77eb07e 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -92,9 +92,6 @@
         boolean isEnabled = !mAllApps.getAppsStore()
                 .hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED);
         int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED;
-        if (getCurrentState() == updatedState) {
-            return;
-        }
         setCurrentState(updatedState);
         resetPrivateSpaceDecorator(updatedState);
     }
@@ -126,19 +123,27 @@
 
     @VisibleForTesting
     void resetPrivateSpaceDecorator(int updatedState) {
+        ActivityAllAppsContainerView<?>.AdapterHolder mainAdapterHolder = mAllApps.mAH.get(MAIN);
         if (updatedState == STATE_ENABLED) {
-            // Add Private Space Decorator to the Recycler view.
+            // Create a new decorator instance if not already available.
             if (mPrivateAppsSectionDecorator == null) {
                 mPrivateAppsSectionDecorator = new PrivateAppsSectionDecorator(
                         mAllApps.mActivityContext,
-                        mAllApps.mAH.get(MAIN).mAppsList);
+                        mainAdapterHolder.mAppsList);
             }
-            mAllApps.mAH.get(MAIN).mRecyclerView.addItemDecoration(mPrivateAppsSectionDecorator);
+            for (int i = 0; i < mainAdapterHolder.mRecyclerView.getItemDecorationCount(); i++) {
+                if (mainAdapterHolder.mRecyclerView.getItemDecorationAt(i)
+                        .equals(mPrivateAppsSectionDecorator)) {
+                    // No need to add another decorator if one is already present in recycler view.
+                    return;
+                }
+            }
+            // Add Private Space Decorator to the Recycler view.
+            mainAdapterHolder.mRecyclerView.addItemDecoration(mPrivateAppsSectionDecorator);
         } else {
             // Remove Private Space Decorator from the Recycler view.
             if (mPrivateAppsSectionDecorator != null) {
-                mAllApps.mAH.get(MAIN).mRecyclerView
-                        .removeItemDecoration(mPrivateAppsSectionDecorator);
+                mainAdapterHolder.mRecyclerView.removeItemDecoration(mPrivateAppsSectionDecorator);
             }
         }
     }
diff --git a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
index 7deb653..8d0cf13 100644
--- a/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/MulticellReorderAlgorithm.java
@@ -48,28 +48,24 @@
     }
 
     @Override
-    public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY) {
+    public ItemConfiguration closestEmptySpaceReorder(ReorderParameters reorderParameters) {
         return removeSeamFromSolution(simulateSeam(
-                () -> super.closestEmptySpaceReorder(pixelX, pixelY, minSpanX, minSpanY, spanX,
-                        spanY)));
+                () -> super.closestEmptySpaceReorder(reorderParameters))
+        );
     }
 
     @Override
-    public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
-            ItemConfiguration solution) {
+    public ItemConfiguration findReorderSolution(ReorderParameters reorderParameters,
+            boolean decX) {
         return removeSeamFromSolution(simulateSeam(
-                () -> super.findReorderSolution(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
-                        direction, dragView, decX, solution)));
+                () -> super.findReorderSolution(reorderParameters, decX)));
     }
 
     @Override
-    public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
-            int spanY,
-            View dragView) {
-        return removeSeamFromSolution(simulateSeam(
-                () -> super.dropInPlaceSolution(pixelX, pixelY, spanX, spanY, dragView)));
+    public ItemConfiguration dropInPlaceSolution(ReorderParameters reorderParameters) {
+        return removeSeamFromSolution(
+                simulateSeam(() -> super.dropInPlaceSolution(reorderParameters))
+        );
     }
 
     void addSeam() {
diff --git a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
index 6682f32..42b6991 100644
--- a/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
+++ b/src/com/android/launcher3/celllayout/ReorderAlgorithm.java
@@ -45,36 +45,28 @@
      * This method differs from closestEmptySpaceReorder and dropInPlaceSolution because this method
      * will move items around and will change the shape of the item if possible to try to find a
      * solution.
-     *
+     * <p>
      * When changing the size of the widget this method will try first subtracting -1 in the x
      * dimension and then subtracting -1 in the y dimension until finding a possible solution or
      * until it no longer can reduce the span.
      *
-     * @param pixelX    X coordinate in pixels in the screen
-     * @param pixelY    Y coordinate in pixels in the screen
-     * @param minSpanX  minimum possible horizontal span it will try to find a solution for.
-     * @param minSpanY  minimum possible vertical span it will try to find a solution for.
-     * @param spanX     horizontal cell span
-     * @param spanY     vertical cell span
-     * @param direction direction in which it will try to push the items intersecting the desired
-     *                  view
-     * @param dragView  view being dragged in reorder
-     * @param decX      whether it will decrease the horizontal or vertical span if it can't find a
-     *                  solution for the current span.
-     * @param solution  variable to store the solution
+     * @param decX     whether it will decrease the horizontal or vertical span if it can't find a
+     *                 solution for the current span.
      * @return the same solution variable
      */
-    public ItemConfiguration findReorderSolution(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
-            ItemConfiguration solution) {
-        return findReorderSolutionRecursive(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY,
-                direction, dragView, decX, solution);
+    public ItemConfiguration findReorderSolution(ReorderParameters reorderParameters,
+            boolean decX) {
+        return findReorderSolutionRecursive(reorderParameters.getPixelX(),
+                reorderParameters.getPixelY(), reorderParameters.getMinSpanX(),
+                reorderParameters.getMinSpanY(), reorderParameters.getSpanX(),
+                reorderParameters.getSpanY(), mCellLayout.mDirectionVector,
+                reorderParameters.getDragView(), decX, reorderParameters.getSolution());
     }
 
 
-    private ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY,
-            int minSpanX, int minSpanY, int spanX, int spanY, int[] direction, View dragView,
-            boolean decX, ItemConfiguration solution) {
+    private ItemConfiguration findReorderSolutionRecursive(int pixelX, int pixelY, int minSpanX,
+            int minSpanY, int spanX, int spanY, int[] direction, View dragView, boolean decX,
+            ItemConfiguration solution) {
         // Copy the current state into the solution. This solution will be manipulated as necessary.
         mCellLayout.copyCurrentStateToSolution(solution);
         // Copy the current occupied array into the temporary occupied array. This array will be
@@ -89,8 +81,8 @@
         boolean success;
         // First we try the exact nearest position of the item being dragged,
         // we will then want to try to move this around to other neighbouring positions
-        success = rearrangementExists(result[0], result[1], spanX, spanY, direction,
-                dragView, solution);
+        success = rearrangementExists(result[0], result[1], spanX, spanY, direction, dragView,
+                solution);
 
         if (!success) {
             // We try shrinking the widget down to size in an alternating pattern, shrink 1 in
@@ -135,10 +127,11 @@
         // and not by the views hash which is "random".
         // The views are sorted twice, once for the X position and a second time for the Y position
         // to ensure same order everytime.
-        Comparator comparator = Comparator.comparing(view ->
-                        ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX())
-                .thenComparing(view ->
-                        ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY());
+        Comparator comparator = Comparator.comparing(
+                view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellX()
+        ).thenComparing(
+                view -> ((CellLayoutLayoutParams) ((View) view).getLayoutParams()).getCellY()
+        );
         List<View> views = solution.map.keySet().stream().sorted(comparator).toList();
         for (View child : views) {
             if (child == ignoreView) continue;
@@ -158,15 +151,13 @@
         // First we try to find a solution which respects the push mechanic. That is,
         // we try to find a solution such that no displaced item travels through another item
         // without also displacing that item.
-        if (attemptPushInDirection(intersectingViews, occupiedRect, direction,
-                ignoreView,
+        if (attemptPushInDirection(intersectingViews, occupiedRect, direction, ignoreView,
                 solution)) {
             return true;
         }
 
         // Next we try moving the views as a block, but without requiring the push mechanic.
-        if (addViewsToTempLocation(intersectingViews, occupiedRect, direction,
-                ignoreView,
+        if (addViewsToTempLocation(intersectingViews, occupiedRect, direction, ignoreView,
                 solution)) {
             return true;
         }
@@ -180,15 +171,15 @@
         return true;
     }
 
-    private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop,
-            int[] direction, ItemConfiguration currentState) {
+    private boolean addViewToTempLocation(View v, Rect rectOccupiedByPotentialDrop, int[] direction,
+            ItemConfiguration currentState) {
         CellAndSpan c = currentState.map.get(v);
         boolean success = false;
         mCellLayout.mTmpOccupied.markCells(c, false);
         mCellLayout.mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
 
-        int[] tmpLocation = mCellLayout.findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY,
-                direction, mCellLayout.mTmpOccupied.cells, null, new int[2]);
+        int[] tmpLocation = findNearestArea(c.cellX, c.cellY, c.spanX, c.spanY, direction,
+                mCellLayout.mTmpOccupied.cells, null, new int[2]);
 
         if (tmpLocation[0] >= 0 && tmpLocation[1] >= 0) {
             c.cellX = tmpLocation[0];
@@ -305,16 +296,16 @@
             int temp = direction[1];
             direction[1] = 0;
 
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             direction[1] = temp;
             temp = direction[0];
             direction[0] = 0;
 
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // Revert the direction
@@ -325,16 +316,16 @@
             direction[1] *= -1;
             temp = direction[1];
             direction[1] = 0;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
 
             direction[1] = temp;
             temp = direction[0];
             direction[0] = 0;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // revert the direction
@@ -345,15 +336,15 @@
         } else {
             // If the direction vector has a single non-zero component, we push first in the
             // direction of the vector
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // Then we try the opposite direction
             direction[0] *= -1;
             direction[1] *= -1;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // Switch the direction back
@@ -367,16 +358,16 @@
             int temp = direction[1];
             direction[1] = direction[0];
             direction[0] = temp;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
 
             // Then we try the opposite direction
             direction[0] *= -1;
             direction[1] *= -1;
-            if (pushViewsToTempLocation(intersectingViews, occupied, direction,
-                    ignoreView, solution)) {
+            if (pushViewsToTempLocation(intersectingViews, occupied, direction, ignoreView,
+                    solution)) {
                 return true;
             }
             // Switch the direction back
@@ -419,7 +410,7 @@
 
         mCellLayout.mTmpOccupied.markCells(rectOccupiedByPotentialDrop, true);
 
-        int[] tmpLocation = mCellLayout.findNearestArea(boundingRect.left, boundingRect.top,
+        int[] tmpLocation = findNearestArea(boundingRect.left, boundingRect.top,
                 boundingRect.width(), boundingRect.height(), direction,
                 mCellLayout.mTmpOccupied.cells, blockOccupied.cells, new int[2]);
 
@@ -446,63 +437,59 @@
     /**
      * Returns a "reorder" if there is empty space without rearranging anything.
      *
-     * @param pixelX   X coordinate in pixels in the screen
-     * @param pixelY   Y coordinate in pixels in the screen
-     * @param spanX    horizontal cell span
-     * @param spanY    vertical cell span
-     * @param dragView view being dragged in reorder
      * @return the configuration that represents the found reorder
      */
-    public ItemConfiguration dropInPlaceSolution(int pixelX, int pixelY, int spanX,
-            int spanY, View dragView) {
-        int[] result = mCellLayout.findNearestAreaIgnoreOccupied(pixelX, pixelY, spanX, spanY,
-                new int[2]);
+    public ItemConfiguration dropInPlaceSolution(ReorderParameters reorderParameters) {
+        int[] result = mCellLayout.findNearestAreaIgnoreOccupied(reorderParameters.getPixelX(),
+                reorderParameters.getPixelY(), reorderParameters.getSpanX(),
+                reorderParameters.getSpanY(), new int[2]);
         ItemConfiguration solution = new ItemConfiguration();
         mCellLayout.copyCurrentStateToSolution(solution);
 
         solution.isSolution = !isConfigurationRegionOccupied(
-                new Rect(result[0], result[1], result[0] + spanX, result[1] + spanY),
-                solution,
-                dragView
-        );
+                new Rect(result[0], result[1], result[0] + reorderParameters.getSpanX(),
+                        result[1] + reorderParameters.getSpanY()), solution,
+                reorderParameters.getDragView());
         if (!solution.isSolution) {
             return solution;
         }
         solution.cellX = result[0];
         solution.cellY = result[1];
-        solution.spanX = spanX;
-        solution.spanY = spanY;
+        solution.spanX = reorderParameters.getSpanX();
+        solution.spanY = reorderParameters.getSpanY();
         return solution;
     }
 
-    private boolean isConfigurationRegionOccupied(Rect region,
-            ItemConfiguration configuration, View ignoreView) {
-        return configuration.map.entrySet()
+    private boolean isConfigurationRegionOccupied(Rect region, ItemConfiguration configuration,
+            View ignoreView) {
+        return configuration.map
+                .entrySet()
                 .stream()
                 .filter(entry -> entry.getKey() != ignoreView)
                 .map(Entry::getValue)
-                .anyMatch(cellAndSpan -> region.intersect(cellAndSpan.cellX, cellAndSpan.cellY,
+                .anyMatch(cellAndSpan -> region.intersect(
+                        cellAndSpan.cellX,
+                        cellAndSpan.cellY,
                         cellAndSpan.cellX + cellAndSpan.spanX,
-                        cellAndSpan.cellY + cellAndSpan.spanY));
+                        cellAndSpan.cellY + cellAndSpan.spanY
+                        )
+                );
     }
 
     /**
      * Returns a "reorder" where we simply drop the item in the closest empty space, without moving
      * any other item in the way.
      *
-     * @param pixelX X coordinate in pixels in the screen
-     * @param pixelY Y coordinate in pixels in the screen
-     * @param spanX  horizontal cell span
-     * @param spanY  vertical cell span
      * @return the configuration that represents the found reorder
      */
-    public ItemConfiguration closestEmptySpaceReorder(int pixelX, int pixelY,
-            int minSpanX, int minSpanY, int spanX, int spanY) {
+    public ItemConfiguration closestEmptySpaceReorder(ReorderParameters reorderParameters) {
         ItemConfiguration solution = new ItemConfiguration();
         int[] result = new int[2];
         int[] resultSpan = new int[2];
-        mCellLayout.findNearestVacantArea(pixelX, pixelY, minSpanX, minSpanY, spanX, spanY, result,
-                resultSpan);
+        mCellLayout.findNearestVacantArea(reorderParameters.getPixelX(),
+                reorderParameters.getPixelY(), reorderParameters.getMinSpanX(),
+                reorderParameters.getMinSpanY(), reorderParameters.getSpanX(),
+                reorderParameters.getSpanY(), result, resultSpan);
         if (result[0] >= 0 && result[1] >= 0) {
             mCellLayout.copyCurrentStateToSolution(solution);
             solution.cellX = result[0];
@@ -521,32 +508,19 @@
      * the workspace to make space for the new item, this function return a solution for that
      * reorder.
      *
-     * @param pixelX   X coordinate in the screen of the dragView in pixels
-     * @param pixelY   Y coordinate in the screen of the dragView in pixels
-     * @param minSpanX minimum horizontal span the item can be shrunk to
-     * @param minSpanY minimum vertical span the item can be shrunk to
-     * @param spanX    occupied horizontal span
-     * @param spanY    occupied vertical span
-     * @param dragView the view of the item being draged
      * @return returns a solution for the given parameters, the solution contains all the icons and
      * the locations they should be in the given solution.
      */
-    public ItemConfiguration calculateReorder(int pixelX, int pixelY, int minSpanX,
-            int minSpanY, int spanX, int spanY, View dragView) {
-        mCellLayout.getDirectionVectorForDrop(pixelX, pixelY, spanX, spanY, dragView,
-                mCellLayout.mDirectionVector);
+    public ItemConfiguration calculateReorder(ReorderParameters reorderParameters) {
+        getDirectionVectorForDrop(reorderParameters, mCellLayout.mDirectionVector);
 
-        ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(pixelX, pixelY, spanX, spanY,
-                dragView);
+        ItemConfiguration dropInPlaceSolution = dropInPlaceSolution(reorderParameters);
 
         // Find a solution involving pushing / displacing any items in the way
-        ItemConfiguration swapSolution = findReorderSolution(pixelX, pixelY, minSpanX,
-                minSpanY, spanX, spanY, mCellLayout.mDirectionVector, dragView, true,
-                new ItemConfiguration());
+        ItemConfiguration swapSolution = findReorderSolution(reorderParameters, true);
 
         // We attempt the approach which doesn't shuffle views at all
-        ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(pixelX, pixelY, minSpanX,
-                minSpanY, spanX, spanY);
+        ItemConfiguration closestSpaceSolution = closestEmptySpaceReorder(reorderParameters);
 
         // If the reorder solution requires resizing (shrinking) the item being dropped, we instead
         // favor a solution in which the item is not resized, but
@@ -559,4 +533,150 @@
         }
         return null;
     }
+
+    /*
+     * Returns a pair (x, y), where x,y are in {-1, 0, 1} corresponding to vector between
+     * the provided point and the provided cell
+     */
+    private void computeDirectionVector(float deltaX, float deltaY, int[] result) {
+        double angle = Math.atan(deltaY / deltaX);
+
+        result[0] = 0;
+        result[1] = 0;
+        if (Math.abs(Math.cos(angle)) > 0.5f) {
+            result[0] = (int) Math.signum(deltaX);
+        }
+        if (Math.abs(Math.sin(angle)) > 0.5f) {
+            result[1] = (int) Math.signum(deltaY);
+        }
+    }
+
+    /**
+     * This seems like it should be obvious and straight-forward, but when the direction vector
+     * needs to match with the notion of the dragView pushing other views, we have to employ
+     * a slightly more subtle notion of the direction vector. The question is what two points is
+     * the vector between? The center of the dragView and its desired destination? Not quite, as
+     * this doesn't necessarily coincide with the interaction of the dragView and items occupying
+     * those cells. Instead we use some heuristics to often lock the vector to up, down, left
+     * or right, which helps make pushing feel right.
+     */
+    public void getDirectionVectorForDrop(ReorderParameters reorderParameters,
+            int[] resultDirection) {
+
+        //TODO(adamcohen) b/151776141 use the items visual center for the direction vector
+        int[] targetDestination = new int[2];
+
+        mCellLayout.findNearestAreaIgnoreOccupied(reorderParameters.getPixelX(),
+                reorderParameters.getPixelY(), reorderParameters.getSpanX(),
+                reorderParameters.getSpanY(), targetDestination);
+        Rect dragRect = new Rect();
+        mCellLayout.cellToRect(targetDestination[0], targetDestination[1],
+                reorderParameters.getSpanX(), reorderParameters.getSpanY(), dragRect);
+        dragRect.offset(reorderParameters.getPixelX() - dragRect.centerX(),
+                reorderParameters.getPixelY() - dragRect.centerY());
+
+        Rect region = new Rect(targetDestination[0], targetDestination[1],
+                targetDestination[0] + reorderParameters.getSpanX(),
+                targetDestination[1] + reorderParameters.getSpanY());
+        Rect dropRegionRect = mCellLayout.getIntersectingRectanglesInRegion(region,
+                reorderParameters.getDragView());
+        if (dropRegionRect == null) dropRegionRect = new Rect(region);
+
+        int dropRegionSpanX = dropRegionRect.width();
+        int dropRegionSpanY = dropRegionRect.height();
+
+        mCellLayout.cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
+                dropRegionRect.height(), dropRegionRect);
+
+        int deltaX = (dropRegionRect.centerX() - reorderParameters.getPixelX())
+                / reorderParameters.getSpanX();
+        int deltaY = (dropRegionRect.centerY() - reorderParameters.getPixelY())
+                / reorderParameters.getSpanY();
+
+        if (dropRegionSpanX == mCellLayout.getCountX()
+                || reorderParameters.getSpanX() == mCellLayout.getCountX()) {
+            deltaX = 0;
+        }
+        if (dropRegionSpanY == mCellLayout.getCountY()
+                || reorderParameters.getSpanY() == mCellLayout.getCountY()) {
+            deltaY = 0;
+        }
+
+        if (deltaX == 0 && deltaY == 0) {
+            // No idea what to do, give a random direction.
+            resultDirection[0] = 1;
+            resultDirection[1] = 0;
+        } else {
+            computeDirectionVector(deltaX, deltaY, resultDirection);
+        }
+    }
+
+    /**
+     * Find a vacant area that will fit the given bounds nearest the requested
+     * cell location, and will also weigh in a suggested direction vector of the
+     * desired location. This method computers distance based on unit grid distances,
+     * not pixel distances.
+     *
+     * @param cellX         The X cell nearest to which you want to search for a vacant area.
+     * @param cellY         The Y cell nearest which you want to search for a vacant area.
+     * @param spanX         Horizontal span of the object.
+     * @param spanY         Vertical span of the object.
+     * @param direction     The favored direction in which the views should move from x, y
+     * @param occupied      The array which represents which cells in the CellLayout are occupied
+     * @param blockOccupied The array which represents which cells in the specified block (cellX,
+     *                      cellY, spanX, spanY) are occupied. This is used when try to move a group
+     *                      of views.
+     * @param result        Array in which to place the result, or null (in which case a new array
+     *                      will
+     *                      be allocated)
+     * @return The X, Y cell of a vacant area that can contain this object,
+     * nearest the requested location.
+     */
+    public int[] findNearestArea(int cellX, int cellY, int spanX, int spanY, int[] direction,
+            boolean[][] occupied, boolean[][] blockOccupied, int[] result) {
+        // Keep track of best-scoring drop area
+        final int[] bestXY = result != null ? result : new int[2];
+        float bestDistance = Float.MAX_VALUE;
+        int bestDirectionScore = Integer.MIN_VALUE;
+
+        final int countX = mCellLayout.getCountX();
+        final int countY = mCellLayout.getCountY();
+
+        for (int y = 0; y < countY - (spanY - 1); y++) {
+            inner:
+            for (int x = 0; x < countX - (spanX - 1); x++) {
+                // First, let's see if this thing fits anywhere
+                for (int i = 0; i < spanX; i++) {
+                    for (int j = 0; j < spanY; j++) {
+                        if (occupied[x + i][y + j] && (blockOccupied == null
+                                || blockOccupied[i][j])) {
+                            continue inner;
+                        }
+                    }
+                }
+
+                float distance = (float) Math.hypot(x - cellX, y - cellY);
+                int[] curDirection = new int[2];
+                computeDirectionVector(x - cellX, y - cellY, curDirection);
+                // The direction score is just the dot product of the two candidate direction
+                // and that passed in.
+                int curDirectionScore =
+                        direction[0] * curDirection[0] + direction[1] * curDirection[1];
+                if (Float.compare(distance, bestDistance) < 0 || (Float.compare(distance,
+                        bestDistance) == 0 && curDirectionScore > bestDirectionScore)) {
+                    bestDistance = distance;
+                    bestDirectionScore = curDirectionScore;
+                    bestXY[0] = x;
+                    bestXY[1] = y;
+                }
+            }
+        }
+
+        // Return -1, -1 if no suitable location found
+        if (bestDistance == Float.MAX_VALUE) {
+            bestXY[0] = -1;
+            bestXY[1] = -1;
+        }
+        return bestXY;
+    }
 }
diff --git a/src/com/android/launcher3/celllayout/ReorderParameters.kt b/src/com/android/launcher3/celllayout/ReorderParameters.kt
new file mode 100644
index 0000000..3fdf35c
--- /dev/null
+++ b/src/com/android/launcher3/celllayout/ReorderParameters.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.celllayout
+
+import android.view.View
+
+class ReorderParameters(
+    val pixelX: Int,
+    val pixelY: Int,
+    val spanX: Int,
+    val spanY: Int,
+    val minSpanX: Int,
+    val minSpanY: Int,
+    val dragView: View?,
+    val solution: ItemConfiguration
+) {}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 15cc6e3..bed6efb 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -124,6 +124,10 @@
             getReleaseFlag(308693847, "ANIMATE_LPNH", TEAMFOOD,
                     "Animates navbar when long pressing");
 
+    public static final BooleanFlag SHRINK_NAV_HANDLE_ON_PRESS =
+            getReleaseFlag(314158312, "SHRINK_NAV_HANDLE_ON_PRESS", DISABLED,
+                    "Shrinks navbar when long pressing if ANIMATE_LPNH is enabled");
+
     public static final IntFlag LPNH_SLOP_PERCENTAGE =
             getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
                     "Controls touch slop percentage for lpnh");
@@ -287,7 +291,7 @@
                     "Enables haptic hint while long pressing on the bottom bar nav handle.");
 
     public static final BooleanFlag ENABLE_SEARCH_HAPTIC_COMMIT =
-            getReleaseFlag(314005577, "ENABLE_SEARCH_HAPTIC_COMMIT", DISABLED,
+            getReleaseFlag(314005577, "ENABLE_SEARCH_HAPTIC_COMMIT", ENABLED,
                     "Enables haptic hint at end of long pressing on the bottom bar nav handle.");
 
     public static final IntFlag LPNH_HAPTIC_HINT_START_SCALE_PERCENT =
diff --git a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
index 57fab2d..2dc8d81 100644
--- a/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/ItemFocusIndicatorHelper.java
@@ -29,8 +29,7 @@
 import android.util.FloatProperty;
 import android.view.View;
 
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.util.Themes;
+import com.android.launcher3.R;
 
 /**
  * A helper class to draw background of a focused item.
@@ -103,9 +102,8 @@
 
         setAlpha(0);
         mShift = 0;
-        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-            mRadius = Themes.getDialogCornerRadius(container.getContext());
-        }
+        mRadius = container.getResources().getDimensionPixelSize(
+                R.dimen.grid_visualization_rounding_radius);
     }
 
     protected void setAlpha(float alpha) {
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 0438e57..1b1d347 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -104,6 +104,15 @@
                 return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true);
             }
 
+            case TestProtocol.REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED: {
+                final Bundle bundle = getLauncherUIProperty(Bundle::putBoolean, l -> l.isStarted());
+                if (bundle != null) return bundle;
+
+                // If Launcher activity wasn't created, it's not started.
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false);
+                return response;
+            }
+
             case TestProtocol.REQUEST_FREEZE_APP_LIST:
                 return getLauncherUIProperty(Bundle::putBoolean, l -> {
                     l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index e32fec2..f283fb6 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -240,7 +240,7 @@
 
     /** Indicates that search has been invoked. */
     public void vibrateForSearch() {
-        if (mSearchEffect != null && FeatureFlags.ENABLE_SEARCH_HAPTIC_COMMIT.get()) {
+        if (mSearchEffect != null) {
             vibrate(mSearchEffect);
         }
     }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 5ec1022..9855b20 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -105,6 +105,7 @@
             mNavBarScrimPaint.setColor(navBarScrimColor);
             invalidate();
         }
+        setupNavBarColor();
     }
 
     @Override
@@ -218,10 +219,18 @@
     }
 
     protected void setupNavBarColor() {
-        boolean isSheetDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark);
-        getSystemUiController().updateUiState(
-                SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
-                isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
+        boolean isNavBarDark = Themes.getAttrBoolean(getContext(), R.attr.isMainColorDark);
+
+        // In light mode, landscape reverses navbar background color.
+        boolean isPhoneLandscape =
+                !mActivityContext.getDeviceProfile().isTablet && mInsets.bottom == 0;
+        if (!isNavBarDark && isPhoneLandscape) {
+            isNavBarDark = true;
+        }
+
+        getSystemUiController().updateUiState(SystemUiController.UI_STATE_WIDGET_BOTTOM_SHEET,
+                isNavBarDark ? SystemUiController.FLAG_DARK_NAV
+                        : SystemUiController.FLAG_LIGHT_NAV);
     }
 
     protected SystemUiController getSystemUiController() {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 953ecda..da90f17 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -24,7 +24,6 @@
 
 import android.animation.Animator;
 import android.content.Context;
-import android.content.pm.LauncherApps;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -95,7 +94,7 @@
     private static final long FADE_IN_DURATION = 150;
     private static final long EDUCATION_TIP_DELAY_MS = 200;
     private static final long EDUCATION_DIALOG_DELAY_MS = 500;
-    private static final float VERTICAL_START_POSITION = 0.3f;
+
     // The widget recommendation table can easily take over the entire screen on devices with small
     // resolution or landscape on phone. This ratio defines the max percentage of content area that
     // the table can display.
@@ -106,9 +105,7 @@
     private final UserHandle mCurrentUser = Process.myUserHandle();
     private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter =
             entry -> mCurrentUser.equals(entry.mPkgItem.user);
-    private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter =
-            entry -> !mCurrentUser.equals(entry.mPkgItem.user)
-                    && !mUserManagerState.isUserQuiet(entry.mPkgItem.user);
+    private final Predicate<WidgetsListBaseEntry> mWorkWidgetsFilter;
     protected final boolean mHasWorkProfile;
     protected boolean mHasRecommendedWidgets;
     protected final SparseArray<AdapterHolder> mAdapters = new SparseArray();
@@ -182,20 +179,23 @@
     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         mDeviceProfile = mActivityContext.getDeviceProfile();
-        mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
         mOrientation = context.getResources().getConfiguration().orientation;
+        mUserCache = UserCache.INSTANCE.get(context);
+        mHasWorkProfile = mUserCache.getUserProfiles()
+                .stream()
+                .anyMatch(user -> mUserCache.getUserInfo(user).isWork());
+        mWorkWidgetsFilter = entry -> mHasWorkProfile
+                && mUserCache.getUserInfo(entry.mPkgItem.user).isWork();
         mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
         mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
         mAdapters.put(AdapterHolder.SEARCH, new AdapterHolder(AdapterHolder.SEARCH));
 
         Resources resources = getResources();
+        mUserManagerState.init(UserCache.INSTANCE.get(context),
+                context.getSystemService(UserManager.class));
         mTabsHeight = mHasWorkProfile
                 ? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
                 : 0;
-
-        mUserCache = UserCache.INSTANCE.get(context);
-        mUserManagerState.init(UserCache.INSTANCE.get(context),
-                context.getSystemService(UserManager.class));
     }
 
     public WidgetsFullSheet(Context context, AttributeSet attrs) {
@@ -622,7 +622,6 @@
         if (animate) {
             if (getPopupContainer().getInsets().bottom > 0) {
                 mContent.setAlpha(0);
-                setTranslationShift(VERTICAL_START_POSITION);
             }
             setUpOpenAnimation(mActivityContext.getDeviceProfile().bottomSheetOpenDuration);
             Animator animator = mOpenCloseAnimation.getAnimationPlayer();
diff --git a/tests/assets/ReorderWidgets/full_reorder_case b/tests/assets/ReorderWidgets/full_reorder_case
index 33ebaae..850e4fd 100644
--- a/tests/assets/ReorderWidgets/full_reorder_case
+++ b/tests/assets/ReorderWidgets/full_reorder_case
@@ -20,7 +20,7 @@
 bbmm
 iimm
 iiaa
-arguments: 0 3
+arguments: 0 2
 board: 4x4
 xxxx
 bbii
diff --git a/tests/assets/ReorderWidgets/simple_reorder_case b/tests/assets/ReorderWidgets/simple_reorder_case
index f5eb7b6..2c50ce4 100644
--- a/tests/assets/ReorderWidgets/simple_reorder_case
+++ b/tests/assets/ReorderWidgets/simple_reorder_case
@@ -34,7 +34,7 @@
 --mm
 --mm
 ----
-arguments: 3 3
+arguments: 2 2
 board: 4x4
 xxxx
 ----
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index b940ba1..7621acd 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -30,6 +30,8 @@
     public static final String FOLDER_OPENED_MESSAGE = "TAPL_FOLDER_OPENED";
     public static final String SEARCH_RESULT_COMPLETE = "SEARCH_RESULT_COMPLETE";
     public static final String LAUNCHER_ACTIVITY_STOPPED_MESSAGE = "TAPL_LAUNCHER_ACTIVITY_STOPPED";
+    public static final String WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE =
+            "TAPL_WALLPAPER_OPEN_ANIMATION_FINISHED";
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
@@ -86,6 +88,8 @@
     public static final String REQUEST_ICON_HEIGHT =
             "icon-height";
     public static final String REQUEST_IS_LAUNCHER_INITIALIZED = "is-launcher-initialized";
+    public static final String REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED =
+            "is-launcher-activity-started";
     public static final String REQUEST_FREEZE_APP_LIST = "freeze-app-list";
     public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
     public static final String REQUEST_ENABLE_MANUAL_TASKBAR_STASHING = "enable-taskbar-stashing";
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index 28471f6..e1af774 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -191,9 +191,21 @@
 
         int[] testCaseXYinPixels = new int[2];
         cl.regionToCenterPoint(x, y, spanX, spanY, testCaseXYinPixels);
-        ItemConfiguration solution = cl.createReorderAlgorithm().calculateReorder(
-                testCaseXYinPixels[0], testCaseXYinPixels[1], minSpanX, minSpanY, spanX, spanY,
-                null);
+        ItemConfiguration configuration = new ItemConfiguration();
+        cl.copyCurrentStateToSolution(configuration);
+        ItemConfiguration solution = cl.createReorderAlgorithm()
+                .calculateReorder(
+                        new ReorderParameters(
+                                testCaseXYinPixels[0],
+                                testCaseXYinPixels[1],
+                                spanX,
+                                spanY,
+                                minSpanX,
+                                minSpanY,
+                                null,
+                                configuration
+                        )
+                );
         if (solution == null) {
             solution = new ItemConfiguration();
             solution.isSolution = false;
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 5f536c7..8ad2249 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -590,6 +590,12 @@
         getInstrumentation().getTargetContext().startActivity(intent);
         assertTrue("App didn't start: " + selector,
                 TestHelpers.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT));
+
+        // Wait for the Launcher to stop.
+        final LauncherInstrumentation launcherInstrumentation = new LauncherInstrumentation();
+        Wait.atMost("Launcher activity didn't stop",
+                () -> !launcherInstrumentation.isLauncherActivityStarted(),
+                DEFAULT_ACTIVITY_TIMEOUT, launcherInstrumentation);
     }
 
     public static ActivityInfo resolveSystemAppInfo(String category) {
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index b2ce400..cce4c5b 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -113,9 +113,11 @@
     }
 
     private void setResult(boolean success) {
-        getInstrumentation().getTargetContext().sendBroadcast(
-                WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
-                        success ? "clickOK" : "clickCancel"));
+        mLauncher.executeAndWaitForWallpaperAnimation(() ->
+                        getInstrumentation().getTargetContext().sendBroadcast(
+                                WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
+                                        success ? "clickOK" : "clickCancel")),
+                "setting widget coinfig result");
     }
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 5b5077e..f8e0f3d 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -585,6 +585,7 @@
         if (hasSystemLauncherObject(OVERVIEW_RES_ID)) return "Overview";
         if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
         if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
+        if (hasLauncherObject(TASKBAR_RES_ID)) return "Taskbar";
         if (mDevice.hasObject(By.pkg(getLauncherPackageName()).depth(0))) {
             return "<Launcher in invalid state>";
         }
@@ -944,6 +945,12 @@
         fail("Launcher didn't initialize");
     }
 
+    public boolean isLauncherActivityStarted() {
+        return getTestInfo(
+                TestProtocol.REQUEST_IS_LAUNCHER_LAUNCHER_ACTIVITY_STARTED).
+                getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     Parcelable executeAndWaitForLauncherEvent(Runnable command,
             UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message,
             String actionName) {
@@ -2306,4 +2313,14 @@
         }
         return result;
     }
+
+    /** Executes a runnable and waits for the wallpaper-open animation completion. */
+    public void executeAndWaitForWallpaperAnimation(Runnable r, String actionName) {
+        executeAndWaitForLauncherEvent(
+                () -> r.run(),
+                event -> TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE
+                        .equals(event.getClassName().toString()),
+                () -> "Didn't detect finishing wallpaper-open animation",
+                actionName);
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 8a34f0d..068482e 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
 import android.graphics.Rect;
 
 import androidx.annotation.NonNull;
@@ -192,8 +190,8 @@
     private List<Integer> getCurrentTasksCenterXList() {
         return mLauncher.isTablet()
                 ? mOverview.getCurrentTasksForTablet().stream()
-                    .map(OverviewTask::getTaskCenterX)
-                    .collect(Collectors.toList())
+                .map(OverviewTask::getTaskCenterX)
+                .collect(Collectors.toList())
                 : List.of(mOverview.getCurrentTask().getTaskCenterX());
     }
 
@@ -203,11 +201,11 @@
     public LaunchedAppState open() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             verifyActiveContainer();
-            mLauncher.executeAndWaitForEvent(
+            mLauncher.executeAndWaitForLauncherEvent(
                     () -> mLauncher.clickLauncherObject(mTask),
-                    event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                    () -> "Launching task didn't open a new window: "
-                            + mTask.getParent().getContentDescription(),
+                    event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE
+                            .equals(event.getClassName().toString()),
+                    () -> "Launcher activity didn't stop",
                     "clicking an overview task");
             if (mOverview.getContainerType()
                     == LauncherInstrumentation.ContainerType.SPLIT_SCREEN_SELECT) {
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
index 38cc321..4a0131b 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -20,6 +20,8 @@
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.shared.TestProtocol;
+
 /** Represents the menu of an overview task. */
 public class OverviewTaskMenu {
 
@@ -59,8 +61,13 @@
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "before tapping the app info menu item")) {
-            mLauncher.clickLauncherObject(
-                    mLauncher.findObjectInContainer(mMenu, By.text("App info")));
+            mLauncher.executeAndWaitForLauncherEvent(
+                    () -> mLauncher.clickLauncherObject(
+                            mLauncher.findObjectInContainer(mMenu, By.text("App info"))),
+                    event -> TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE
+                            .equals(event.getClassName().toString()),
+                    () -> "Launcher activity didn't stop",
+                    "tapped app info menu item");
 
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
                     "tapped app info menu item")) {