Merge "App Pairs: Implement app pairs icon" into main
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 42e6809..00a282a 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -15,8 +15,10 @@
  */
 package com.android.launcher3.statehandlers;
 
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
+import android.os.Debug;
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.View;
@@ -46,6 +48,7 @@
 
     private boolean mFreeformTasksVisible;
     private boolean mInOverviewState;
+    private boolean mBackgroundStateEnabled;
     private boolean mGestureInProgress;
 
     @Nullable
@@ -113,7 +116,11 @@
      * Whether freeform windows are visible in desktop mode.
      */
     public boolean areFreeformTasksVisible() {
-        return mFreeformTasksVisible;
+        if (DEBUG) {
+            Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + mFreeformTasksVisible
+                    + " overview=" + mInOverviewState);
+        }
+        return mFreeformTasksVisible && !mInOverviewState;
     }
 
     /**
@@ -121,7 +128,8 @@
      */
     public void setFreeformTasksVisible(boolean freeformTasksVisible) {
         if (DEBUG) {
-            Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible);
+            Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible
+                    + " currentValue=" + mFreeformTasksVisible);
         }
         if (!isDesktopModeSupported()) {
             return;
@@ -146,11 +154,21 @@
     }
 
     /**
-     * Sets whether the overview is visible and updates launcher visibility based on that.
+     * Process launcher state change and update launcher view visibility based on desktop state
      */
-    public void setOverviewStateEnabled(boolean overviewStateEnabled) {
+    public void onLauncherStateChanged(LauncherState state) {
         if (DEBUG) {
-            Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled);
+            Log.d(TAG, "onLauncherStateChanged: newState=" + state);
+        }
+        setBackgroundStateEnabled(state == BACKGROUND_APP);
+        // Desktop visibility tracks overview and background state separately
+        setOverviewStateEnabled(state != BACKGROUND_APP && state.overviewUi);
+    }
+
+    private void setOverviewStateEnabled(boolean overviewStateEnabled) {
+        if (DEBUG) {
+            Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
+                    + " currentValue=" + mInOverviewState);
         }
         if (!isDesktopModeSupported()) {
             return;
@@ -160,7 +178,7 @@
             if (mInOverviewState) {
                 setLauncherViewsVisibility(View.VISIBLE);
                 markLauncherResumed();
-            } else if (mFreeformTasksVisible && !mGestureInProgress) {
+            } else if (areFreeformTasksVisible() && !mGestureInProgress) {
                 // Switching out of overview state and gesture finished.
                 // If freeform tasks are still visible, hide launcher again.
                 setLauncherViewsVisibility(View.INVISIBLE);
@@ -169,6 +187,27 @@
         }
     }
 
+    private void setBackgroundStateEnabled(boolean backgroundStateEnabled) {
+        if (DEBUG) {
+            Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
+                    + " currentValue=" + mBackgroundStateEnabled);
+        }
+        if (!isDesktopModeSupported()) {
+            return;
+        }
+        if (backgroundStateEnabled != mBackgroundStateEnabled) {
+            mBackgroundStateEnabled = backgroundStateEnabled;
+            if (mBackgroundStateEnabled) {
+                setLauncherViewsVisibility(View.VISIBLE);
+                markLauncherResumed();
+            } else if (areFreeformTasksVisible() && !mGestureInProgress) {
+                // Switching out of background state. If freeform tasks are visible, pause launcher.
+                setLauncherViewsVisibility(View.INVISIBLE);
+                markLauncherPaused();
+            }
+        }
+    }
+
     /**
      * Whether recents gesture is currently in progress.
      */
@@ -183,6 +222,9 @@
         if (!isDesktopModeSupported()) {
             return;
         }
+        if (DEBUG) {
+            Log.d(TAG, "setRecentsGestureStart");
+        }
         setRecentsGestureInProgress(true);
     }
 
@@ -194,6 +236,9 @@
         if (!isDesktopModeSupported()) {
             return;
         }
+        if (DEBUG) {
+            Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
+        }
         setRecentsGestureInProgress(false);
 
         if (endTarget == null) {
@@ -203,9 +248,6 @@
     }
 
     private void setRecentsGestureInProgress(boolean gestureInProgress) {
-        if (DEBUG) {
-            Log.d(TAG, "setGestureInProgress: inProgress=" + gestureInProgress);
-        }
         if (gestureInProgress != mGestureInProgress) {
             mGestureInProgress = gestureInProgress;
         }
@@ -222,7 +264,8 @@
 
     private void setLauncherViewsVisibility(int visibility) {
         if (DEBUG) {
-            Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility);
+            Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " "
+                    + Debug.getCaller());
         }
         View workspaceView = mLauncher.getWorkspace();
         if (workspaceView != null) {
@@ -236,7 +279,7 @@
 
     private void markLauncherPaused() {
         if (DEBUG) {
-            Log.d(TAG, "markLauncherPaused");
+            Log.d(TAG, "markLauncherPaused " + Debug.getCaller());
         }
         StatefulActivity<LauncherState> activity =
                 QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
@@ -247,7 +290,7 @@
 
     private void markLauncherResumed() {
         if (DEBUG) {
-            Log.d(TAG, "markLauncherResumed");
+            Log.d(TAG, "markLauncherResumed " + Debug.getCaller());
         }
         StatefulActivity<LauncherState> activity =
                 QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index c4255bf..afefe42 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -107,14 +107,17 @@
         mControllers = controllers;
         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         Resources resources = mActivity.getResources();
+        final int stashedTaskbarHeight;
         if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) {
             mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_size);
             mStashedHandleWidth =
                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
+            stashedTaskbarHeight = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
         } else {
             mTaskbarSize = deviceProfile.taskbarHeight;
             mStashedHandleWidth = resources
                     .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
+            stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
         }
         int taskbarBottomMargin = deviceProfile.taskbarBottomMargin;
         mStashedHandleView.getLayoutParams().height = mTaskbarSize + taskbarBottomMargin;
@@ -123,7 +126,6 @@
                 isPhoneGestureNavMode(deviceProfile) ? 1 : 0);
         mTaskbarStashedHandleHintScale.updateValue(1f);
 
-        final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
         mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 9f8f82a..d4faf47 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
 import static com.android.launcher3.Utilities.isRunningInTestHarness;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
@@ -843,9 +844,17 @@
             return getSetupWindowHeight();
         }
 
-        if (DisplayController.isTransientTaskbar(this)) {
-            return mDeviceProfile.taskbarHeight
-                    + (2 * mDeviceProfile.taskbarBottomMargin)
+        boolean shouldTreatAsTransient = DisplayController.isTransientTaskbar(this)
+                || (ENABLE_TASKBAR_PINNING.get() && !isThreeButtonNav());
+
+        // Return transient taskbar window height when pinning feature is enabled, so taskbar view
+        // does not get cut off during pinning animation.
+        if (shouldTreatAsTransient) {
+            DeviceProfile transientTaskbarDp = mDeviceProfile.toBuilder(this)
+                    .setIsTransientTaskbar(true).build();
+
+            return transientTaskbarDp.taskbarHeight
+                    + (2 * transientTaskbarDp.taskbarBottomMargin)
                     + resources.getDimensionPixelSize(R.dimen.transient_taskbar_shadow_blur);
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 4ad5c88..6ddf9e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -21,6 +21,8 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION;
+import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.EXTENDED_CONTAINERS;
+import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -64,6 +66,7 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -626,7 +629,9 @@
 
         if (tag instanceof ItemInfo) {
             ItemInfo item = (ItemInfo) tag;
-            if (item.container == CONTAINER_ALL_APPS || item.container == CONTAINER_PREDICTION) {
+            if (item.container == CONTAINER_ALL_APPS
+                    || item.container == CONTAINER_PREDICTION
+                    || isInSearchResultContainer(item)) {
                 if (mDisallowGlobalDrag) {
                     // We're dragging in taskbarAllApps, we don't have folders or shortcuts
                     return iconView;
@@ -648,6 +653,13 @@
         return iconView;
     }
 
+    private static boolean isInSearchResultContainer(ItemInfo item) {
+        ContainerInfo containerInfo = item.getContainerInfo();
+        return containerInfo.getContainerCase() == EXTENDED_CONTAINERS
+                && containerInfo.getExtendedContainers().getContainerCase()
+                        == DEVICE_SEARCH_RESULT_CONTAINER;
+    }
+
     private void setupReturnDragAnimator(float fromX, float fromY, View originalView,
             TaskbarReturnPropertiesListener animListener) {
         // Finish any pending return animation before starting a new return
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 88ae349..3bfeee8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -209,7 +210,10 @@
                         updateStateForFlag(FLAG_RESUMED, true);
                     }
                     applyState();
-                    boolean disallowLongClick = finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
+                    boolean disallowLongClick =
+                            FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+                                    ? mLauncher.isSplitSelectionEnabled()
+                                    : finalState == LauncherState.OVERVIEW_SPLIT_SELECT;
                     com.android.launcher3.taskbar.Utilities.setOverviewDragState(
                             mControllers, finalState.disallowTaskbarGlobalDrag(),
                             disallowLongClick, finalState.allowTaskbarInitialSplitSelection());
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index b405320..858141a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -458,7 +458,7 @@
      */
     private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
         PendingAnimation setter = new PendingAnimation(100);
-        if (TaskbarManager.isPhoneButtonNavMode(mActivity)) {
+        if (TaskbarManager.isPhoneMode(launcherDp)) {
             // No animation for icons in small-screen
             return setter.createPlaybackController();
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 5182a32..07d86e4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -158,10 +158,6 @@
         if (mAppsView != null) {
             return;
         }
-        // mControllers and getSharedState should never be null here. Do not handle null-pointer
-        // to catch invalid states.
-        mControllers.getSharedState().allAppsVisible = true;
-
         mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
 
         // Initialize search session for All Apps.
@@ -178,10 +174,7 @@
         // Ensures All Apps gets touch events in case it is not the top floating view. Floating
         // views above it may not be able to intercept the touch, so All Apps should try to.
         mOverlayContext.getDragLayer().addTouchController(mSlideInView);
-        mSlideInView.addOnCloseListener(() -> {
-            mControllers.getSharedState().allAppsVisible = false;
-            cleanUpOverlay();
-        });
+        mSlideInView.addOnCloseListener(this::cleanUpOverlay);
         TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
                 mOverlayContext,
                 mSlideInView,
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
index 6d740c0..cf5fd59 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -18,17 +18,22 @@
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_TASKBAR_ALL_APPS;
 import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.allapps.AllAppsTransitionListener;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.appprediction.AppsDividerView;
 import com.android.launcher3.taskbar.NavbarButtonsViewController;
 import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarSharedState;
 import com.android.launcher3.taskbar.TaskbarStashController;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
 import com.android.launcher3.util.DisplayController;
 
+import java.util.Optional;
+
 /**
  * Handles the {@link TaskbarAllAppsContainerView} behavior and synchronizes its transitions with
  * taskbar stashing.
@@ -41,6 +46,7 @@
     private final TaskbarStashController mTaskbarStashController;
     private final NavbarButtonsViewController mNavbarButtonsViewController;
     private final TaskbarOverlayController mOverlayController;
+    private final @Nullable TaskbarSharedState mTaskbarSharedState;
     private final boolean mShowKeyboard;
 
     TaskbarAllAppsViewController(
@@ -56,6 +62,7 @@
         mTaskbarStashController = taskbarControllers.taskbarStashController;
         mNavbarButtonsViewController = taskbarControllers.navbarButtonsViewController;
         mOverlayController = taskbarControllers.taskbarOverlayController;
+        mTaskbarSharedState = taskbarControllers.getSharedState();
         mShowKeyboard = showKeyboard;
 
         mSlideInView.init(new TaskbarAllAppsCallbacks(searchSessionController));
@@ -87,8 +94,10 @@
             mTaskbarStashController.applyState();
         }
 
+        Optional.ofNullable(mTaskbarSharedState).ifPresent(s -> s.allAppsVisible = true);
         mNavbarButtonsViewController.setSlideInViewVisible(true);
         mSlideInView.setOnCloseBeginListener(() -> {
+            Optional.ofNullable(mTaskbarSharedState).ifPresent(s -> s.allAppsVisible = false);
             mNavbarButtonsViewController.setSlideInViewVisible(false);
             AbstractFloatingView.closeOpenContainer(
                     mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index 9758d44..7529508 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -46,7 +46,7 @@
         protected val startContextualContainer: ViewGroup,
         protected val imeSwitcher: ImageView?,
         protected val rotationButton: RotationButton?,
-        protected val a11yButton: ImageView
+        protected val a11yButton: ImageView?
 ) : NavButtonLayoutter {
     protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
     protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index f254ee8..e7847f3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -35,7 +35,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -114,7 +114,9 @@
             startContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        endContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            endContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             endContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 7db1a37..c502cdb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -58,7 +58,7 @@
                 navButtonsView: FrameLayout,
                 imeSwitcher: ImageView?,
                 rotationButton: RotationButton?,
-                a11yButton: ImageView,
+                a11yButton: ImageView?,
                 resources: Resources,
                 isKidsMode: Boolean,
                 isInSetup: Boolean,
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index c1dae40..d6ea7f0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -31,7 +31,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
         AbstractNavButtonLayoutter(
                 resources,
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index efe9d4b..54edd21 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -36,7 +36,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView,
+        a11yButton: ImageView?,
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -113,7 +113,9 @@
             startContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        startContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            startContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             startContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index ad03e5b..34df282 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -35,7 +35,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView,
+        a11yButton: ImageView?,
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -111,7 +111,9 @@
             endContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        endContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            endContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             endContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index fd6e991..cfe1276 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -31,7 +31,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
         PhoneLandscapeNavLayoutter(
                 resources,
@@ -63,7 +63,9 @@
             endContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        endContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            endContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             endContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index db245b8..36f5150 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -32,7 +32,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -72,7 +72,9 @@
             startContextualContainer.addView(imeSwitcher)
             imeSwitcher.layoutParams = getParamsToCenterView()
         }
-        endContextualContainer.addView(a11yButton)
+        if (a11yButton != null) {
+            endContextualContainer.addView(a11yButton)
+        }
         if (rotationButton != null) {
             endContextualContainer.addView(rotationButton.currentView)
             rotationButton.currentView.layoutParams = getParamsToCenterView()
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 56e55bb..58dce69 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -26,7 +26,10 @@
 import com.android.launcher3.R
 import com.android.systemui.shared.rotation.RotationButton
 
-/** Layoutter for showing 3 button navigation on large screen */
+/**
+ * Layoutter for rendering task bar in large screen. {@param isContextualButtonShowing} is true in
+ * 3-button mode.
+ */
 class TaskbarNavLayoutter(
         resources: Resources,
         navBarContainer: LinearLayout,
@@ -34,7 +37,7 @@
         startContextualContainer: ViewGroup,
         imeSwitcher: ImageView?,
         rotationButton: RotationButton?,
-        a11yButton: ImageView
+        a11yButton: ImageView?
 ) :
     AbstractNavButtonLayoutter(
             resources,
@@ -89,24 +92,28 @@
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
 
-        val endContextualContainerParams = FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
-        endContextualContainerParams.gravity = Gravity.END or Gravity.CENTER_VERTICAL
-        endContextualContainer.layoutParams = endContextualContainerParams
+        if (isContextualButtonShowing) {
+            val endContextualContainerParams = FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+            endContextualContainerParams.gravity = Gravity.END or Gravity.CENTER_VERTICAL
+            endContextualContainer.layoutParams = endContextualContainerParams
 
-        val startContextualContainerParams = FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
-        startContextualContainerParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
-        startContextualContainer.layoutParams = startContextualContainerParams
+            val startContextualContainerParams = FrameLayout.LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+            startContextualContainerParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+            startContextualContainer.layoutParams = startContextualContainerParams
 
-        if (imeSwitcher != null) {
-            startContextualContainer.addView(imeSwitcher)
-            imeSwitcher.layoutParams = getParamsToCenterView()
-        }
-        endContextualContainer.addView(a11yButton)
-        if (rotationButton != null) {
-            endContextualContainer.addView(rotationButton.currentView)
-            rotationButton.currentView.layoutParams = getParamsToCenterView()
+            if (imeSwitcher != null) {
+                startContextualContainer.addView(imeSwitcher)
+                imeSwitcher.layoutParams = getParamsToCenterView()
+            }
+            if (a11yButton != null) {
+                endContextualContainer.addView(a11yButton)
+            }
+            if (rotationButton != null) {
+                endContextualContainer.addView(rotationButton.currentView)
+                rotationButton.currentView.layoutParams = getParamsToCenterView()
+            }
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 18e9cdd..a71333a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -219,8 +219,6 @@
     private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
     private SplitToWorkspaceController mSplitToWorkspaceController;
 
-    private AsyncClockEventDelegate mAsyncClockEventDelegate;
-
     /**
      * If Launcher restarted while in the middle of an Overview split select, it needs this data to
      * recover. In all other cases this will remain null.
@@ -498,10 +496,6 @@
             mSplitSelectStateController.onDestroy();
         }
 
-        if (mAsyncClockEventDelegate != null) {
-            mAsyncClockEventDelegate.onDestroy();
-        }
-
         super.onDestroy();
         mHotseatPredictionController.destroy();
         mSplitWithKeyboardShortcutController.onDestroy();
@@ -695,7 +689,7 @@
     }
 
     @Override
-    protected boolean isSplitSelectionEnabled() {
+    public boolean isSplitSelectionEnabled() {
         return mSplitSelectStateController.isSplitSelectActive();
     }
 
@@ -993,6 +987,13 @@
                 .playPlaceholderDismissAnim(this);
     }
 
+    @Override
+    public void dismissSplitSelection() {
+        super.dismissSplitSelection();
+        mSplitSelectStateController.getSplitAnimationController()
+                .playPlaceholderDismissAnim(this);
+    }
+
     public <T extends OverviewActionsView> T getActionsView() {
         return (T) mActionsView;
     }
@@ -1351,18 +1352,12 @@
         switch (name) {
             case "TextClock", "android.widget.TextClock" -> {
                 TextClock tc = new TextClock(context, attrs);
-                if (mAsyncClockEventDelegate == null) {
-                    mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
-                }
-                tc.setClockEventDelegate(mAsyncClockEventDelegate);
+                tc.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
                 return tc;
             }
             case "AnalogClock", "android.widget.AnalogClock" -> {
                 AnalogClock ac = new AnalogClock(context, attrs);
-                if (mAsyncClockEventDelegate == null) {
-                    mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
-                }
-                ac.setClockEventDelegate(mAsyncClockEventDelegate);
+                ac.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
                 return ac;
             }
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 1d55da3..3e7d45e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -21,9 +21,9 @@
 import android.graphics.Rect;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Flags;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -72,7 +72,7 @@
 
     @Override
     public boolean isTaskbarStashed(Launcher launcher) {
-        if (Flags.enableGridOnlyOverview()) {
+        if (FeatureFlags.enableGridOnlyOverview()) {
             return true;
         }
         return super.isTaskbarStashed(launcher);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index e788cc4..4dfa81d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2084,18 +2084,9 @@
     }
 
     private void finishCurrentTransitionToRecents() {
-        if (mRecentsView != null
-                && mActivityInterface.getDesktopVisibilityController() != null
-                && mActivityInterface.getDesktopVisibilityController().areFreeformTasksVisible()) {
-            mRecentsView.switchToScreenshot(() -> {
-                mRecentsView.finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
-                        () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
-            });
-        } else {
-            mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
-            if (mRecentsAnimationController != null) {
-                mRecentsAnimationController.detachNavigationBarFromApp(true);
-            }
+        mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.detachNavigationBarFromApp(true);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 25389c5..8925bd6 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -50,10 +50,10 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.BaseState;
@@ -242,7 +242,7 @@
     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
             PagedOrientationHandler orientedState) {
         if (dp.isTablet) {
-            if (Flags.enableGridOnlyOverview()) {
+            if (FeatureFlags.enableGridOnlyOverview()) {
                 calculateGridTaskSize(context, dp, outRect, orientedState);
             } else {
                 calculateFocusTaskSize(context, dp, outRect);
@@ -339,7 +339,7 @@
             PagedOrientationHandler orientedState) {
         Resources res = context.getResources();
         Rect potentialTaskRect = new Rect();
-        if (Flags.enableGridOnlyOverview()) {
+        if (FeatureFlags.enableGridOnlyOverview()) {
             calculateGridSize(dp, potentialTaskRect);
         } else {
             calculateFocusTaskSize(context, dp, potentialTaskRect);
@@ -371,7 +371,7 @@
     public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect,
             PagedOrientationHandler orientedState) {
         calculateTaskSize(context, dp, outRect, orientedState);
-        boolean isGridOnlyOverview = dp.isTablet && Flags.enableGridOnlyOverview();
+        boolean isGridOnlyOverview = dp.isTablet && FeatureFlags.enableGridOnlyOverview();
         int claimedSpaceBelow = isGridOnlyOverview
                 ? dp.overviewActionsTopMarginPx + dp.overviewActionsHeight + dp.stashedTaskbarHeight
                 : (dp.heightPx - outRect.bottom - dp.getInsets().bottom);
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 23def14..f898e2f 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -126,4 +126,14 @@
         }
         return name.toString();
     }
+
+    /**
+     * Returns an input consumer of the given class type, or null if none is found.
+     */
+    default <T extends InputConsumer> T getInputConsumerOfClass(Class<T> c) {
+        if (getClass().equals(c)) {
+            return c.cast(this);
+        }
+        return null;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 3d332c2..5d26ec0 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -115,7 +115,8 @@
                     /* extras= */ 0,
                     /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
             notifyAnimationCanceled();
-            animationController.finish(false /* toHome */, false /* sendUserLeaveHint */);
+            animationController.finish(false /* toHome */, false /* sendUserLeaveHint */,
+                    null /* finishCb */);
             return;
         }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 8972dc8..341e18c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -21,6 +21,7 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
 
 import android.content.Context;
+import android.os.Bundle;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRecentsAnimationController;
@@ -32,6 +33,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
+import com.android.internal.os.IResultReceiver;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
@@ -172,12 +174,19 @@
         mFinishTargetIsLauncher = toRecents;
         mOnFinishedListener.accept(this);
         Runnable finishCb = () -> {
-            mController.finish(toRecents, sendUserLeaveHint);
+            mController.finish(toRecents, sendUserLeaveHint, new IResultReceiver.Stub() {
+                @Override
+                public void send(int i, Bundle bundle) throws RemoteException {
+                    ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation-callback");
+                    MAIN_EXECUTOR.execute(() -> {
+                        mPendingFinishCallbacks.executeAllAndDestroy();
+                    });
+                }
+            });
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
             InteractionJankMonitorWrapper.end(
                     InteractionJankMonitorWrapper.CUJ_APP_SWIPE_TO_RECENTS);
-            MAIN_EXECUTOR.execute(mPendingFinishCallbacks::executeAllAndDestroy);
         };
         if (forceFinish) {
             finishCb.run();
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 1448a52..3fdc7ba 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -66,6 +66,7 @@
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -239,6 +240,7 @@
     public void onDisplayInfoChanged(Context context, Info info, int flags) {
         if ((flags & (CHANGE_ROTATION | CHANGE_NAVIGATION_MODE)) != 0) {
             mMode = info.navigationMode;
+            ActiveGestureLog.INSTANCE.setIsFullyGesturalNavMode(isFullyGesturalNavMode());
             mNavBarPosition = new NavBarPosition(mMode, info);
 
             if (mMode == NO_BUTTON) {
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index e358a8e..073dea0 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -17,7 +17,7 @@
 
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
@@ -78,6 +78,8 @@
     private final TaskThumbnailCache mThumbnailCache;
     private final ComponentCallbacks mCallbacks;
 
+    private final TaskStackChangeListeners mTaskStackChangeListeners;
+
     private RecentsModel(Context context) {
         this(context, new IconProvider(context));
     }
@@ -89,13 +91,14 @@
                         SystemUiProxy.INSTANCE.get(context)),
                 new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider),
                 new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
-                iconProvider);
+                iconProvider,
+                TaskStackChangeListeners.getInstance());
     }
 
     @VisibleForTesting
     RecentsModel(Context context, RecentTasksList taskList, TaskIconCache iconCache,
-            TaskThumbnailCache thumbnailCache,
-            IconProvider iconProvider) {
+            TaskThumbnailCache thumbnailCache, IconProvider iconProvider,
+            TaskStackChangeListeners taskStackChangeListeners) {
         mContext = context;
         mTaskList = taskList;
         mIconCache = iconCache;
@@ -117,7 +120,8 @@
             mCallbacks = null;
         }
 
-        TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
+        mTaskStackChangeListeners = taskStackChangeListeners;
+        mTaskStackChangeListeners.registerTaskStackListener(this);
         iconProvider.registerIconChangeListener(this, MAIN_EXECUTOR.getHandler());
     }
 
@@ -358,6 +362,8 @@
         if (mCallbacks != null) {
             mContext.unregisterComponentCallbacks(mCallbacks);
         }
+        mIconCache.removeTaskVisualsChangeListener();
+        mTaskStackChangeListeners.unregisterTaskStackListener(this);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index f5a7ecc..4b47209 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -42,7 +42,6 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -119,7 +118,8 @@
             }
         }
         // But force-finish it anyways
-        finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */);
+        finishRunningRecentsAnimation(false /* toHome */, true /* forceFinish */,
+                null /* forceFinishCb */);
 
         if (mCallbacks != null) {
             // If mCallbacks still != null, that means we are getting this startRecentsAnimation()
@@ -261,12 +261,6 @@
             // to let the transition controller collect Home activity.
             CachedTaskInfo cti = gestureState.getRunningTask();
             boolean homeIsOnTop = cti != null && cti.isHomeTask();
-            if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
-                if (cti != null && cti.isFreeformTask()) {
-                    // No transient launch when desktop task is on top
-                    homeIsOnTop = true;
-                }
-            }
             if (activityInterface.allowAllAppsFromOverview()) {
                 homeIsOnTop = true;
             }
@@ -325,19 +319,20 @@
      * Finishes the running recents animation.
      */
     public void finishRunningRecentsAnimation(boolean toHome) {
-        finishRunningRecentsAnimation(toHome, false /* forceFinish */);
+        finishRunningRecentsAnimation(toHome, false /* forceFinish */, null /* forceFinishCb */);
     }
 
     /**
      * Finishes the running recents animation.
      * @param forceFinish will synchronously finish the controller
      */
-    public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish) {
+    public void finishRunningRecentsAnimation(boolean toHome, boolean forceFinish,
+            Runnable forceFinishCb) {
         if (mController != null) {
             ActiveGestureLog.INSTANCE.addLog(
                     /* event= */ "finishRunningRecentsAnimation", toHome);
             if (forceFinish) {
-                mController.finishController(toHome, null, false /* sendUserLeaveHint */,
+                mController.finishController(toHome, forceFinishCb, false /* sendUserLeaveHint */,
                         true /* forceFinish */);
             } else {
                 Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), toHome
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 7d03d77..bd3ccb7 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -34,8 +34,8 @@
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Flags;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
@@ -80,7 +80,7 @@
         boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0;
         boolean isTablet = activity.getDeviceProfile().isTablet;
 
-        boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
+        boolean isGridOnlyOverview = isTablet && FeatureFlags.enableGridOnlyOverview();
         // Add overview actions to the menu when in in-place rotate landscape mode, or in
         // grid-only overview.
         if ((!canLauncherRotate && isInLandscape) || isGridOnlyOverview) {
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index e5fca4b..2ca9f99 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -15,7 +15,7 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
 
 import android.content.Context;
 import android.content.res.Resources;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 63771f0..5557639 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -48,6 +48,14 @@
      */
     protected abstract String getDelegatorName();
 
+    @Override
+    public <T extends InputConsumer> T getInputConsumerOfClass(Class<T> c) {
+        if (getClass().equals(c)) {
+            return c.cast(this);
+        }
+        return mDelegate.getInputConsumerOfClass(c);
+    }
+
     protected void setActive(MotionEvent ev) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(getDelegatorName())
                 .append(" became active"));
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index addcfb8..fc3f3ab 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -52,9 +52,15 @@
                 if (isInArea(motionEvent.getRawX())) {
                     Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
                     if (longPressRunnable != null) {
-                        setActive(motionEvent);
-
-                        MAIN_EXECUTOR.post(longPressRunnable);
+                        OtherActivityInputConsumer oaic = getInputConsumerOfClass(
+                                OtherActivityInputConsumer.class);
+                        if (oaic != null) {
+                            oaic.setForceFinishRecentsTransitionCallback(longPressRunnable);
+                            setActive(motionEvent);
+                        } else {
+                            setActive(motionEvent);
+                            MAIN_EXECUTOR.post(longPressRunnable);
+                        }
                     }
                 }
             }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 7e61167..28c00eb 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -125,6 +125,9 @@
     // Might be displacement in X or Y, depending on the direction we are swiping from the nav bar.
     private float mStartDisplacement;
 
+    // The callback called upon finishing the recents transition if it was force-canceled
+    private Runnable mForceFinishRecentsTransitionCallback;
+
     public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
@@ -444,7 +447,7 @@
                     // animateToProgress so we have to manually finish here. In the case of
                     // ACTION_CANCEL, someone else may be doing something so finish synchronously.
                     mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */,
-                            isCanceled /* forceFinish */);
+                            isCanceled /* forceFinish */, mForceFinishRecentsTransitionCallback);
                 } else {
                     // The animation hasn't started yet, so insert a replacement handler into the
                     // callbacks which immediately finishes the animation after it starts.
@@ -513,6 +516,14 @@
     }
 
     /**
+     * Sets a callback to be called when the recents transition is force-canceled by another input
+     * consumer being made active.
+     */
+    public void setForceFinishRecentsTransitionCallback(Runnable r) {
+        mForceFinishRecentsTransitionCallback = r;
+    }
+
+    /**
      * A listener which just finishes the animation immediately after starting. Replaces
      * AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
      */
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 3388642..7d3a860 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -21,7 +21,9 @@
 import android.media.session.MediaSessionManager;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
@@ -52,6 +54,7 @@
     private final boolean mStartingInActivityBounds;
     private boolean mTargetHandledTouch;
     private boolean mHasSetTouchModeForFirstDPadEvent;
+    private boolean mIsWaitingForAttachToWindow;
 
     public OverviewInputConsumer(GestureState gestureState, T activity,
             @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
@@ -118,21 +121,47 @@
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                if (!mHasSetTouchModeForFirstDPadEvent) {
-                    // When Overview is launched via meta+tab or swipe up from an app, the touch
-                    // mode somehow is not changed to false by the Android framework. The subsequent
-                    // key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched to focused
-                    // views, while focus can only be requested in
-                    // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To
-                    // note, here we launch overview with live tile.
-                    mHasSetTouchModeForFirstDPadEvent = true;
-                    mActivity.getRootView().getViewRootImpl().touchModeChanged(false);
+                if (mHasSetTouchModeForFirstDPadEvent) {
+                    break;
                 }
+                View viewRoot = mActivity.getRootView();
+                if (viewRoot.isAttachedToWindow()) {
+                    setTouchModeChanged(viewRoot);
+                    break;
+                }
+                if (mIsWaitingForAttachToWindow) {
+                    break;
+                }
+                mIsWaitingForAttachToWindow = true;
+                viewRoot.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View view) {
+                        view.removeOnAttachStateChangeListener(this);
+                        mIsWaitingForAttachToWindow = false;
+                        setTouchModeChanged(viewRoot);
+                    }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View view) {
+                        // Do nothing
+                    }
+                });
                 break;
             default:
                 break;
         }
         mActivity.dispatchKeyEvent(ev);
     }
+
+    private void setTouchModeChanged(@NonNull View viewRoot) {
+        // When Overview is launched via meta+tab or swipe up from an app, the touch
+        // mode somehow is not changed to false by the Android framework. The
+        // subsequent key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched
+        // to focused views, while focus can only be requested in
+        // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To
+        // note, here we launch overview with live tile.
+        mHasSetTouchModeForFirstDPadEvent = true;
+        viewRoot.getViewRootImpl().touchModeChanged(false);
+    }
 }
 
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 20fa921..ee72144 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -74,6 +74,11 @@
             @NonNull PrintWriter writer,
             @NonNull ActiveGestureLog.EventLog eventLog) {
         writer.println(prefix + "Error messages for gesture ID: " + eventLog.logId);
+        if (!eventLog.mIsFullyGesturalNavMode) {
+            writer.println(prefix
+                    + "\tSkipping gesture error detection because gesture navigation not enabled");
+            return;
+        }
 
         boolean errorDetected = false;
         // Use a Set since the order is inherently checked in the loop.
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 7103e63..ca686ec 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -35,6 +35,8 @@
 
     public static final ActiveGestureLog INSTANCE = new ActiveGestureLog();
 
+    private boolean mIsFullyGesturalNavMode;
+
     /**
      * NOTE: This value should be kept same as
      * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform
@@ -127,7 +129,7 @@
             @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
         if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) {
-            EventLog eventLog = new EventLog(mCurrentLogId);
+            EventLog eventLog = new EventLog(mCurrentLogId, mIsFullyGesturalNavMode);
             EventEntry eventEntry = new EventEntry();
 
             eventEntry.update(type, event, extras, compoundString, gestureEvent);
@@ -214,6 +216,10 @@
         return mCurrentLogId++;
     }
 
+    public void setIsFullyGesturalNavMode(boolean isFullyGesturalNavMode) {
+        mIsFullyGesturalNavMode = isFullyGesturalNavMode;
+    }
+
     /** Returns the current log ID. This should be used when a log trace is being reused. */
     public int getLogId() {
         return mCurrentLogId;
@@ -277,9 +283,11 @@
 
         protected final List<EventEntry> eventEntries = new ArrayList<>();
         protected final int logId;
+        protected final boolean mIsFullyGesturalNavMode;
 
-        private EventLog(int logId) {
+        private EventLog(int logId, boolean isFullyGesturalNavMode) {
             this.logId = logId;
+            mIsFullyGesturalNavMode = isFullyGesturalNavMode;
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index 0dee5b3..cda87c0 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -32,6 +32,8 @@
 
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SettingsCache.OnChangeListener;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -42,7 +44,11 @@
 /**
  * Extension of {@link ClockEventDelegate} to support async event registration
  */
-public class AsyncClockEventDelegate extends ClockEventDelegate implements OnChangeListener {
+public class AsyncClockEventDelegate extends ClockEventDelegate
+        implements OnChangeListener, SafeCloseable {
+
+    public static final MainThreadInitializedObject<AsyncClockEventDelegate> INSTANCE =
+            new MainThreadInitializedObject<>(AsyncClockEventDelegate::new);
 
     private final Context mContext;
     private final SimpleBroadcastReceiver mReceiver =
@@ -55,7 +61,7 @@
     private boolean mFormatRegistered = false;
     private boolean mDestroyed = false;
 
-    public AsyncClockEventDelegate(Context context) {
+    private AsyncClockEventDelegate(Context context) {
         super(context);
         mContext = context;
 
@@ -79,6 +85,9 @@
 
     @Override
     public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
+        if (mDestroyed) {
+            return;
+        }
         synchronized (mFormatObservers) {
             if (!mFormatRegistered && !mDestroyed) {
                 SettingsCache.INSTANCE.get(mContext).register(mFormatUri, this);
@@ -114,10 +123,8 @@
         }
     }
 
-    /**
-     * Unregisters all system callbacks and destroys this delegate
-     */
-    public void onDestroy() {
+    @Override
+    public void close() {
         mDestroyed = true;
         SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
         UI_HELPER_EXECUTOR.execute(() -> mReceiver.unregisterReceiverSafely(mContext));
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 46f6e89..45c63b2 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -118,7 +118,7 @@
     private final Handler mHandler;
     private final RecentsModel mRecentTasksModel;
     @Nullable
-    private final Runnable mActivityBackCallback;
+    private Runnable mActivityBackCallback;
     private final SplitAnimationController mSplitAnimationController;
     private final AppPairsController mAppPairsController;
     private final SplitSelectDataHolder mSplitSelectDataHolder;
@@ -185,6 +185,7 @@
 
     public void onDestroy() {
         mContext = null;
+        mActivityBackCallback = null;
     }
 
     /**
@@ -602,7 +603,7 @@
 
         private final int mInitialTaskId;
         private final int mSecondTaskId;
-        private final Consumer<Boolean> mSuccessCallback;
+        private Consumer<Boolean> mSuccessCallback;
 
         RemoteSplitLaunchTransitionRunner(int initialTaskId, int secondTaskId,
                 @Nullable Consumer<Boolean> callback) {
@@ -629,6 +630,7 @@
                             finishAdapter.run();
                             if (mSuccessCallback != null) {
                                 mSuccessCallback.accept(true);
+                                mSuccessCallback = null;
                             }
                             resetState();
                         });
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index fba847f..cfb4d0d 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index dc6b5a2..9ff990e 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -18,7 +18,6 @@
 
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 
 import android.content.Context;
@@ -41,7 +40,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
@@ -338,16 +336,18 @@
     @Override
     public RunnableList launchTaskAnimated() {
         RunnableList endCallback = new RunnableList();
-        endCallback.add(() -> Launcher.getLauncher(mActivity).getStateManager().goToState(NORMAL));
 
+        RecentsView recentsView = getRecentsView();
         DesktopRecentsTransitionController recentsController =
-                getRecentsView().getDesktopRecentsController();
+                recentsView.getDesktopRecentsController();
         if (recentsController != null) {
             recentsController.launchDesktopFromRecents(this, success -> {
                 endCallback.executeAllAndDestroy();
             });
         }
 
+        // Callbacks get run from recentsView for case when recents animation already running
+        recentsView.addSideTaskLaunchCallback(endCallback);
         return endCallback;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 1867fe9..fb9e640 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -137,16 +137,16 @@
     @Override
     public void onStateTransitionStart(LauncherState toState) {
         setOverviewStateEnabled(toState.overviewUi);
-        if (toState.overviewUi) {
-            // If overview is enabled, we want to update at the start
-            updateOverviewStateForDesktop(true);
-        }
+
         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
         setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
         if (toState == OVERVIEW_MODAL_TASK) {
             setOverviewSelectEnabled(true);
         }
         setFreezeViewVisibility(true);
+        if (mActivity.getDesktopVisibilityController() != null) {
+            mActivity.getDesktopVisibilityController().onLauncherStateChanged(toState);
+        }
     }
 
     @Override
@@ -167,11 +167,6 @@
             runActionOnRemoteHandles(remoteTargetHandle ->
                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
         }
-
-        if (!finalState.overviewUi) {
-            // If overview is disabled, we want to update at the end
-            updateOverviewStateForDesktop(false);
-        }
     }
 
     @Override
@@ -183,9 +178,6 @@
                     & CLEAR_ALL_BUTTON) != 0;
             setDisallowScrollToClearAll(!hasClearAllButton);
         }
-        if (mActivity.getDesktopVisibilityController() != null) {
-            mActivity.getDesktopVisibilityController().setOverviewStateEnabled(enabled);
-        }
     }
 
     @Override
@@ -282,11 +274,4 @@
                     null /* transition */);
         }
     }
-
-    private void updateOverviewStateForDesktop(boolean enabled) {
-        DesktopVisibilityController controller = mActivity.getDesktopVisibilityController();
-        if (controller != null) {
-            controller.setOverviewStateEnabled(enabled);
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 7f1d619..9141c99 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -29,9 +29,9 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -289,7 +289,7 @@
             return 0;
         }
 
-        if (mDp.isTablet && Flags.enableGridOnlyOverview()) {
+        if (mDp.isTablet && FeatureFlags.enableGridOnlyOverview()) {
             return mDp.stashedTaskbarHeight;
         }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2aba4d0..4136a89 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -42,7 +42,7 @@
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index df907e7..5298eea 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -77,7 +77,6 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -115,8 +114,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 
-import kotlin.Unit;
-
 import java.lang.annotation.Retention;
 import java.util.Arrays;
 import java.util.Collections;
@@ -125,6 +122,8 @@
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 
+import kotlin.Unit;
+
 /**
  * A task in the Recents view.
  */
@@ -1146,7 +1145,7 @@
         } else if (dp.isTablet) {
             int alignedOptionIndex = 0;
             if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) {
-                if (Flags.enableGridOnlyOverview()) {
+                if (FeatureFlags.enableGridOnlyOverview()) {
                     // With no focused task, there is less available space below the tasks, so align
                     // the arrow to the third option in the menu.
                     alignedOptionIndex = 2;
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index 8c13fe3..f3115c6 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -24,11 +24,11 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class FallbackTaskbarUIControllerTest : TaskbarBaseTestCase() {
@@ -36,8 +36,8 @@
     lateinit var fallbackTaskbarUIController: FallbackTaskbarUIController
     lateinit var stateListener: StateManager.StateListener<RecentsState>
 
-    @Mock lateinit var recentsActivity: RecentsActivity
-    @Mock lateinit var stateManager: StateManager<RecentsState>
+    private val recentsActivity: RecentsActivity = mock()
+    private val stateManager: StateManager<RecentsState> = mock()
 
     @Before
     override fun setup() {
@@ -46,10 +46,10 @@
         fallbackTaskbarUIController = FallbackTaskbarUIController(recentsActivity)
 
         // Capture registered state listener to send events to in our tests
-        val captor = ArgumentCaptor.forClass(StateManager.StateListener::class.java)
+        val captor = argumentCaptor<StateManager.StateListener<RecentsState>>()
         fallbackTaskbarUIController.init(taskbarControllers)
         verify(stateManager).addStateListener(captor.capture())
-        stateListener = captor.value as StateManager.StateListener<RecentsState>
+        stateListener = captor.lastValue
     }
 
     @Test
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
index 148e36c..ed88c29 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
@@ -23,17 +23,17 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 class TaskbarKeyguardControllerTest : TaskbarBaseTestCase() {
 
-    @Mock lateinit var baseDragLayer: TaskbarDragLayer
-    @Mock lateinit var keyguardManager: KeyguardManager
+    private val baseDragLayer: TaskbarDragLayer = mock()
+    private val keyguardManager: KeyguardManager = mock()
 
     @Before
     override fun setup() {
@@ -50,7 +50,7 @@
     @Test
     fun uninterestingFlags_noActions() {
         setFlags(0)
-        verify(navbarButtonsViewController, never()).setKeyguardVisible(anyBoolean(), anyBoolean())
+        verify(navbarButtonsViewController, never()).setKeyguardVisible(any(), any())
     }
 
     @Test
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 16bfe70..37fcf43 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -19,32 +19,29 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class NavButtonLayoutFactoryTest {
 
-    @Mock lateinit var mockDeviceProfile: DeviceProfile
-    @Mock lateinit var mockParentButtonContainer: FrameLayout
-    @Mock lateinit var mockNavLayout: LinearLayout
-    @Mock lateinit var mockStartContextualLayout: ViewGroup
-    @Mock lateinit var mockEndContextualLayout: ViewGroup
-    @Mock lateinit var mockResources: Resources
-    @Mock lateinit var mockBackButton: ImageView
-    @Mock lateinit var mockRecentsButton: ImageView
-    @Mock lateinit var mockHomeButton: ImageView
-    @Mock lateinit var mockImeSwitcher: ImageView
-    @Mock lateinit var mockRotationButton: RotationButton
-    @Mock lateinit var mockA11yButton: ImageView
+    private val mockDeviceProfile: DeviceProfile = mock()
+    private val mockParentButtonContainer: FrameLayout = mock()
+    private val mockNavLayout: LinearLayout = mock()
+    private val mockStartContextualLayout: ViewGroup = mock()
+    private val mockEndContextualLayout: ViewGroup = mock()
+    private val mockResources: Resources = mock()
+    private val mockBackButton: ImageView = mock()
+    private val mockRecentsButton: ImageView = mock()
+    private val mockHomeButton: ImageView = mock()
+    private val mockImeSwitcher: ImageView = mock()
+    private val mockRotationButton: RotationButton = mock()
+    private val mockA11yButton: ImageView = mock()
 
     private var surfaceRotation = Surface.ROTATION_0
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-
         // Init end nav buttons
         whenever(mockNavLayout.childCount).thenReturn(3)
         whenever(mockNavLayout.findViewById<View>(R.id.back)).thenReturn(mockBackButton)
@@ -155,13 +152,13 @@
         mockDeviceProfile.isTaskbarPresent = false
         setDeviceProfileLandscape()
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
-                getLayoutter(
-                        isKidsMode = false,
-                        isInSetup = false,
-                        isThreeButtonNav = true,
-                        phoneMode = true,
-                        surfaceRotation = ROTATION_270
-                )
+            getLayoutter(
+                isKidsMode = false,
+                isInSetup = false,
+                isThreeButtonNav = true,
+                phoneMode = true,
+                surfaceRotation = ROTATION_270
+            )
         assert(layoutter is PhoneSeascapeNavLayoutter)
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
index 08e0898..c552d83 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import org.junit.After;
 import org.junit.Before;
@@ -93,7 +94,7 @@
         when(mThumbnailCache.isPreloadingEnabled()).thenReturn(true);
 
         mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class),
-                mThumbnailCache, mock(IconProvider.class));
+                mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class));
 
         mResource = mock(Resources.class);
         when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 3039261..e8f0447 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -82,8 +82,7 @@
 
     @After
     public void tearDown() {
-        executeOnLauncher(launcher -> {
-            if (launcher == null) return;
+        executeOnLauncherInTearDown(launcher -> {
             RecentsView recentsView = launcher.getOverviewPanel();
             recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
         });
@@ -213,6 +212,7 @@
 
 
     @Test
+    @ScreenRecord // b/303329286
     public void testOverviewActionsMenu_iconAppChipMenu() throws Exception {
         try (AutoCloseable c = TestUtil.overrideFlag(ENABLE_OVERVIEW_ICON_MENU, true)) {
             startTestAppsWithCheck();
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 7e07b81..50803fe 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -32,39 +32,36 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class SplitAnimationControllerTest {
 
     private val taskId = 9
 
-    @Mock lateinit var mockSplitSelectStateController: SplitSelectStateController
+    private val mockSplitSelectStateController: SplitSelectStateController = mock()
     // TaskView
-    @Mock lateinit var mockTaskView: TaskView
-    @Mock lateinit var mockThumbnailView: TaskThumbnailView
-    @Mock lateinit var mockBitmap: Bitmap
-    @Mock lateinit var mockIconView: IconView
-    @Mock lateinit var mockTaskViewDrawable: Drawable
+    private val mockTaskView: TaskView = mock()
+    private val mockThumbnailView: TaskThumbnailView = mock()
+    private val mockBitmap: Bitmap = mock()
+    private val mockIconView: IconView = mock()
+    private val mockTaskViewDrawable: Drawable = mock()
     // GroupedTaskView
-    @Mock lateinit var mockGroupedTaskView: GroupedTaskView
-    @Mock lateinit var mockTask: Task
-    @Mock lateinit var mockTaskKey: Task.TaskKey
-    @Mock lateinit var mockTaskIdAttributeContainer: TaskIdAttributeContainer
+    private val mockGroupedTaskView: GroupedTaskView = mock()
+    private val mockTask: Task = mock()
+    private val mockTaskKey: Task.TaskKey = mock()
+    private val mockTaskIdAttributeContainer: TaskIdAttributeContainer = mock()
 
     // SplitSelectSource
-    @Mock lateinit var splitSelectSource: SplitConfigurationOptions.SplitSelectSource
-    @Mock lateinit var mockSplitSourceDrawable: Drawable
-    @Mock lateinit var mockSplitSourceView: View
+    private val splitSelectSource: SplitConfigurationOptions.SplitSelectSource = mock()
+    private val mockSplitSourceDrawable: Drawable = mock()
+    private val mockSplitSourceView: View = mock()
 
     lateinit var splitAnimationController: SplitAnimationController
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-
         whenever(mockTaskView.thumbnail).thenReturn(mockThumbnailView)
         whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
         whenever(mockTaskView.iconView).thenReturn(mockIconView)
@@ -85,12 +82,14 @@
         // Missing taskView icon
         whenever(mockIconView.drawable).thenReturn(null)
 
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockTaskView }, { splitSelectSource })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
 
-        assertEquals("Did not fallback to use splitSource icon drawable",
-                mockSplitSourceDrawable, splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not fallback to use splitSource icon drawable",
+            mockSplitSourceDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
 
     @Test
@@ -99,12 +98,14 @@
         whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(true)
         whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
 
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockTaskView }, { splitSelectSource })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
 
-        assertEquals("Did not use taskView icon drawable", mockTaskViewDrawable,
-                splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not use taskView icon drawable",
+            mockTaskViewDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
 
     @Test
@@ -116,12 +117,14 @@
         // Set split source to null
         whenever(splitSelectSource.drawable).thenReturn(null)
 
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockTaskView }, { splitSelectSource })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
 
-        assertEquals("Did not use taskView icon drawable", mockTaskViewDrawable,
-                splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not use taskView icon drawable",
+            mockTaskViewDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
 
     @Test
@@ -130,12 +133,14 @@
         whenever(mockSplitSelectStateController.isAnimateCurrentTaskDismissal).thenReturn(false)
         whenever(mockSplitSelectStateController.isDismissingFromSplitPair).thenReturn(false)
 
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockTaskView }, { splitSelectSource })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews({ mockTaskView }, { splitSelectSource })
 
-        assertEquals("Did not use splitSource icon drawable", mockSplitSourceDrawable,
-                splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not use splitSource icon drawable",
+            mockSplitSourceDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
 
     @Test
@@ -154,12 +159,17 @@
         whenever(mockTaskKey.getId()).thenReturn(taskId)
         whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
         whenever(mockGroupedTaskView.taskIdAttributeContainers)
-                .thenReturn(Array(1) { mockTaskIdAttributeContainer })
-        val splitAnimInitProps : SplitAnimationController.Companion.SplitAnimInitProps =
-                splitAnimationController.getFirstAnimInitViews(
-                        { mockGroupedTaskView }, { splitSelectSource })
+            .thenReturn(Array(1) { mockTaskIdAttributeContainer })
+        val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
+            splitAnimationController.getFirstAnimInitViews(
+                { mockGroupedTaskView },
+                { splitSelectSource }
+            )
 
-        assertEquals("Did not use splitSource icon drawable", mockSplitSourceDrawable,
-                splitAnimInitProps.iconDrawable)
+        assertEquals(
+            "Did not use splitSource icon drawable",
+            mockSplitSourceDrawable,
+            splitAnimInitProps.iconDrawable
+        )
     }
-}
\ No newline at end of file
+}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index f198741..f292f9a 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -33,11 +33,11 @@
 import com.android.launcher3.statemanager.StatefulActivity
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.launcher3.util.withArgCaptor
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.SystemUiProxy
 import com.android.systemui.shared.recents.model.Task
 import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
+import java.util.function.Consumer
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertNull
@@ -45,23 +45,22 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
-import java.util.function.Consumer
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class SplitSelectStateControllerTest {
 
-    @Mock lateinit var systemUiProxy: SystemUiProxy
-    @Mock lateinit var depthController: DepthController
-    @Mock lateinit var statsLogManager: StatsLogManager
-    @Mock lateinit var stateManager: StateManager<LauncherState>
-    @Mock lateinit var handler: Handler
-    @Mock lateinit var context: StatefulActivity<*>
-    @Mock lateinit var recentsModel: RecentsModel
-    @Mock lateinit var pendingIntent: PendingIntent
+    private val systemUiProxy: SystemUiProxy = mock()
+    private val depthController: DepthController = mock()
+    private val statsLogManager: StatsLogManager = mock()
+    private val stateManager: StateManager<LauncherState> = mock()
+    private val handler: Handler = mock()
+    private val context: StatefulActivity<*> = mock()
+    private val recentsModel: RecentsModel = mock()
+    private val pendingIntent: PendingIntent = mock()
 
     lateinit var splitSelectStateController: SplitSelectStateController
 
@@ -69,11 +68,12 @@
     private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
 
     private var taskIdCounter = 0
-    private fun getUniqueId(): Int { return ++taskIdCounter }
+    private fun getUniqueId(): Int {
+        return ++taskIdCounter
+    }
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
         splitSelectStateController =
             SplitSelectStateController(
                 context,
@@ -111,13 +111,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(nonMatchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(nonMatchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -162,13 +163,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -201,13 +203,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(nonPrimaryUserComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(nonPrimaryUserComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -255,13 +258,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(nonPrimaryUserComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(nonPrimaryUserComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -306,13 +310,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -327,10 +332,7 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(
-                ComponentName("hotdog", "pie"),
-                ComponentName("pumpkin", "pie")
-            )
+            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
             generateGroupTask(
                 ComponentName("pomegranate", "juice"),
@@ -361,13 +363,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(nonMatchingComponent, matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(nonMatchingComponent, matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -381,10 +384,7 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(
-                ComponentName("hotdog", "pie"),
-                ComponentName("pumpkin", "pie")
-            )
+            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
             generateGroupTask(
                 ComponentName("pomegranate", "juice"),
@@ -415,13 +415,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(matchingComponent, matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(matchingComponent, matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -479,13 +480,14 @@
 
         // Capture callback from recentsModel#getTasks()
         val consumer =
-            withArgCaptor<Consumer<ArrayList<GroupTask>>> {
-                splitSelectStateController.findLastActiveTasksAndRunCallback(
-                    listOf(matchingComponent, matchingComponent),
-                    taskConsumer
-                )
-                verify(recentsModel).getTasks(capture())
-            }
+            argumentCaptor<Consumer<ArrayList<GroupTask>>> {
+                    splitSelectStateController.findLastActiveTasksAndRunCallback(
+                        listOf(matchingComponent, matchingComponent),
+                        taskConsumer
+                    )
+                    verify(recentsModel).getTasks(capture())
+                }
+                .lastValue
 
         // Send our mocked tasks
         consumer.accept(tasks)
@@ -531,7 +533,7 @@
     @Test
     fun secondPendingIntentSet() {
         val itemInfo = ItemInfo()
-        `when`(pendingIntent.creatorUserHandle).thenReturn(primaryUserHandle)
+        whenever(pendingIntent.creatorUserHandle).thenReturn(primaryUserHandle)
         splitSelectStateController.setInitialTaskSelect(null, 0, itemInfo, null, 1)
         splitSelectStateController.setSecondTask(pendingIntent)
         assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
diff --git a/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt b/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
index f73be72..6a418a4 100644
--- a/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/unfold/PreemptiveUnfoldTransitionProgressProviderTest.kt
@@ -21,19 +21,17 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.util.Log
 import androidx.test.filters.SmallTest
-import com.android.launcher3.util.any
-import com.android.launcher3.util.mock
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.anyBoolean
-import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.inOrder
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -74,7 +72,7 @@
         provider.preemptivelyStartTransition(initialProgress = null)
 
         verify(listener).onTransitionStarted()
-        verify(listener, never()).onTransitionProgress(anyFloat())
+        verify(listener, never()).onTransitionProgress(any())
     }
 
     @Test
@@ -90,7 +88,7 @@
         provider.preemptivelyStartTransition()
         provider.cancelPreemptiveStart()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
@@ -111,7 +109,7 @@
         source.onTransitionStarted()
         source.onTransitionFinished()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
@@ -152,7 +150,7 @@
         provider.preemptivelyStartTransition()
         source.onTransitionFinished()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
@@ -165,7 +163,7 @@
         testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
         testableLooper.processAllMessages()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
@@ -178,7 +176,7 @@
         testableLooper.moveTimeForward(PREEMPTIVE_UNFOLD_TIMEOUT_MS + 1)
         testableLooper.processAllMessages()
 
-        verify(testWtfHandler).onTerribleFailure(any(), any(), anyBoolean())
+        verify(testWtfHandler).onTerribleFailure(any(), any(), any())
     }
 
     @Test
@@ -225,7 +223,7 @@
 
         source.onTransitionFinished()
 
-        with(inOrder(listener)) {
+        inOrder(listener) {
             verify(listener).onTransitionStarted()
             verify(listener).onTransitionFinished()
         }
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 7b0d71b..189db21 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -3,6 +3,7 @@
 import static com.android.launcher3.CellLayout.SPRING_LOADED_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.LAYOUT_HEIGHT;
 import static com.android.launcher3.LauncherAnimUtils.LAYOUT_WIDTH;
+import static com.android.launcher3.LauncherPrefs.RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RESIZE_COMPLETED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RESIZE_STARTED;
 import static com.android.launcher3.views.BaseDragLayer.LAYOUT_X;
@@ -55,8 +56,6 @@
     private static final float RESIZE_THRESHOLD = 0.66f;
     private static final int RESIZE_TRANSITION_DURATION_MS = 150;
 
-    private static final String KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
-            "launcher.reconfigurable_widget_education_tip_seen";
     private static final Rect sTmpRect = new Rect();
     private static final Rect sTmpRect2 = new Rect();
 
@@ -276,9 +275,8 @@
             if (!hasSeenReconfigurableWidgetEducationTip()) {
                 post(() -> {
                     if (showReconfigurableWidgetEducationTip() != null) {
-                        mLauncher.getSharedPrefs().edit()
-                                .putBoolean(KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN,
-                                        true).apply();
+                        LauncherPrefs.get(getContext()).put(
+                                RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN, true);
                     }
                 });
             }
@@ -872,8 +870,7 @@
     }
 
     private boolean hasSeenReconfigurableWidgetEducationTip() {
-        return mLauncher.getSharedPrefs()
-                .getBoolean(KEY_RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN, false)
+        return LauncherPrefs.get(getContext()).get(RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN)
                 || Utilities.isRunningInTestHarness();
     }
 }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index c96e22d..9a2193f 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -1772,7 +1772,7 @@
 
     /** Gets the space that the overview actions will take, including bottom margin. */
     public int getOverviewActionsClaimedSpace() {
-        int overviewActionsSpace = isTablet && Flags.enableGridOnlyOverview()
+        int overviewActionsSpace = isTablet && FeatureFlags.enableGridOnlyOverview()
                 ? 0
                 : (overviewActionsTopMarginPx + overviewActionsHeight);
         return overviewActionsSpace + getOverviewActionsClaimedSpaceBelow();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7a16e1f..919ad21 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -28,6 +28,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
 import static com.android.launcher3.BuildConfig.APPLICATION_ID;
+import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
 import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
@@ -44,7 +45,7 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
-import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
 import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
@@ -236,7 +237,6 @@
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 import com.android.systemui.plugins.LauncherOverlayPlugin;
 import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.shared.LauncherExterns;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
 
@@ -258,7 +258,7 @@
  * Default launcher application.
  */
 public class Launcher extends StatefulActivity<LauncherState>
-        implements LauncherExterns, Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
+        implements Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
         PluginListener<LauncherOverlayPlugin> {
     public static final String TAG = "Launcher";
 
@@ -426,6 +426,8 @@
     private BaseSearchConfig mBaseSearchConfig;
     private StartupLatencyLogger mStartupLatencyLogger;
     private CellPosMapper mCellPosMapper = CellPosMapper.DEFAULT;
+    private boolean mIsFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
+            && !ENABLE_SMARTSPACE_REMOVAL.get();
 
     private final CannedAnimationCoordinator mAnimationCoordinator =
             new CannedAnimationCoordinator(this);
@@ -693,7 +695,7 @@
 
     @Override
     public void onPluginConnected(LauncherOverlayPlugin overlayManager, Context context) {
-        switchOverlay(() -> overlayManager.createOverlayManager(this, this));
+        switchOverlay(() -> overlayManager.createOverlayManager(this));
     }
 
     @Override
@@ -1318,7 +1320,9 @@
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
         // default state, otherwise we will update to the wrong offsets in RTL
         mWorkspace.lockWallpaperToDefaultPage();
-        mWorkspace.bindAndInitFirstWorkspaceScreen();
+        if (!ENABLE_SMARTSPACE_REMOVAL.get()) {
+            mWorkspace.bindAndInitFirstWorkspaceScreen();
+        }
         mDragController.addDragListener(mWorkspace);
 
         // Get the search/delete/uninstall bar
@@ -2159,15 +2163,22 @@
     }
 
     @Override
+    public void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) {
+        mIsFirstPagePinnedItemEnabled = isFirstPagePinnedItemEnabled;
+        mWorkspace.bindAndInitFirstWorkspaceScreen();
+    }
+
+    @Override
     public void bindScreens(IntArray orderedScreenIds) {
         mWorkspace.mPageIndicator.setAreScreensBinding(true);
         int firstScreenPosition = 0;
         if ((FeatureFlags.QSB_ON_FIRST_SCREEN
+                && mIsFirstPagePinnedItemEnabled
                 && !shouldShowFirstPageWidget())
                 && orderedScreenIds.indexOf(FIRST_SCREEN_ID) != firstScreenPosition) {
             orderedScreenIds.removeValue(FIRST_SCREEN_ID);
             orderedScreenIds.add(firstScreenPosition, FIRST_SCREEN_ID);
-        } else if ((!FeatureFlags.QSB_ON_FIRST_SCREEN
+        } else if (((!FeatureFlags.QSB_ON_FIRST_SCREEN && !mIsFirstPagePinnedItemEnabled)
                 || shouldShowFirstPageWidget())
                 && orderedScreenIds.isEmpty()) {
             // If there are no screens, we need to have an empty screen
@@ -2218,6 +2229,7 @@
         for (int i = 0; i < count; i++) {
             int screenId = orderedScreenIds.get(i);
             if (FeatureFlags.QSB_ON_FIRST_SCREEN
+                    && mIsFirstPagePinnedItemEnabled
                     && !shouldShowFirstPageWidget()
                     && screenId == FIRST_SCREEN_ID) {
                 // No need to bind the first screen, as its always bound.
@@ -2958,7 +2970,7 @@
                 for (int j = 0; j < layout.getChildCount(); j++) {
                     Object tag = layout.getChildAt(j).getTag();
                     if (tag != null) {
-                        writer.println(prefix + "    " + tag.toString());
+                        writer.println(prefix + "    " + tag);
                     }
                 }
             }
@@ -2968,7 +2980,7 @@
             for (int j = 0; j < layout.getChildCount(); j++) {
                 Object tag = layout.getChildAt(j).getTag();
                 if (tag != null) {
-                    writer.println(prefix + "    " + tag.toString());
+                    writer.println(prefix + "    " + tag);
                 }
             }
         }
@@ -3098,11 +3110,16 @@
     }
 
     /** To be overridden by subclasses */
-    protected boolean isSplitSelectionEnabled() {
+    public boolean isSplitSelectionEnabled() {
         // Overridden
         return false;
     }
 
+    /** Call to dismiss the intermediary split selection state. */
+    public void dismissSplitSelection() {
+        // Overridden; move this into ActivityContext if necessary for Taskbar
+    }
+
     @Override
     public void returnToHomescreen() {
         super.returnToHomescreen();
@@ -3234,7 +3251,6 @@
     /**
      * Call this after onCreate to set or clear overlay.
      */
-    @Override
     public void setLauncherOverlay(LauncherOverlay overlay) {
         mWorkspace.setLauncherOverlay(overlay);
     }
@@ -3342,16 +3358,10 @@
         return mModelWriter;
     }
 
-    @Override
     public SharedPreferences getSharedPrefs() {
         return mSharedPrefs;
     }
 
-    @Override
-    public SharedPreferences getDevicePrefs() {
-        return LauncherPrefs.getDevicePrefs(this);
-    }
-
     public int getOrientation() {
         return mOldConfig.orientation;
     }
@@ -3406,6 +3416,10 @@
         // Overridden
     }
 
+    public boolean getIsFirstPagePinnedItemEnabled() {
+        return mIsFirstPagePinnedItemEnabled;
+    }
+
     /**
      * Returns the animation coordinator for playing one-off animations
      */
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 9db8c82..ab41a31 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -21,6 +21,8 @@
 
 import static com.android.launcher3.LauncherPrefs.ICON_STATE;
 import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
+import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
 
@@ -123,6 +125,23 @@
                 .addUserEventListener(mModel::onUserEvent);
         mOnTerminateCallback.add(userChangeListener::close);
 
+        if (ENABLE_SMARTSPACE_REMOVAL.get()) {
+            OnSharedPreferenceChangeListener firstPagePinnedItemListener =
+                    new OnSharedPreferenceChangeListener() {
+                        @Override
+                        public void onSharedPreferenceChanged(
+                                SharedPreferences sharedPreferences, String key) {
+                            if (SMARTSPACE_ON_HOME_SCREEN.equals(key)) {
+                                mModel.forceReload();
+                            }
+                        }
+                    };
+            LauncherPrefs.getPrefs(mContext).registerOnSharedPreferenceChangeListener(
+                    firstPagePinnedItemListener);
+            mOnTerminateCallback.add(() -> LauncherPrefs.getPrefs(mContext)
+                    .unregisterOnSharedPreferenceChangeListener(firstPagePinnedItemListener));
+        }
+
         LockedUserState.get(context).runOnUserUnlocked(() -> {
             CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
             cwm.setWidgetRefreshCallback(mModel::refreshAndBindWidgetsAndShortcuts);
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 4db2fca..f2df230 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -24,7 +24,6 @@
 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
-import com.android.launcher3.allapps.WorkProfileManager
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.provider.RestoreDbTask
@@ -312,7 +311,7 @@
         val THEMED_ICONS =
             backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
         @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
-        @JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
+        @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
         @JvmField
         val WORKSPACE_SIZE =
             backedUpItem(
@@ -380,6 +379,16 @@
                 encryptionType = EncryptionType.DEVICE_PROTECTED
             )
 
+        // Preferences for widget configurations
+        @JvmField
+        val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
+            backedUpItem("launcher.reconfigurable_widget_education_tip_seen", false)
+        @JvmField
+        val WIDGETS_EDUCATION_DIALOG_SEEN =
+            backedUpItem("launcher.widgets_education_dialog_seen", false)
+        @JvmField
+        val WIDGETS_EDUCATION_TIP_SEEN = backedUpItem("launcher.widgets_education_tip_seen", false)
+
         @VisibleForTesting
         @JvmStatic
         fun <T> backedUpItem(
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index c20d602..eeb5fe0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -28,6 +28,7 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -596,18 +597,19 @@
      * Initializes and binds the first page
      */
     public void bindAndInitFirstWorkspaceScreen() {
-        if (!FeatureFlags.QSB_ON_FIRST_SCREEN
+        if ((!FeatureFlags.QSB_ON_FIRST_SCREEN
+                || !mLauncher.getIsFirstPagePinnedItemEnabled())
                 || shouldShowFirstPageWidget()) {
+            mFirstPagePinnedItem = null;
             return;
         }
 
         // Add the first page
         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
-        // Always add a first page pinned widget on the first screen.
         if (mFirstPagePinnedItem == null) {
             // In transposed layout, we add the first page pinned widget in the Grid.
             // As workspace does not touch the edges, we do not need a full
-            // width first page pinned widget.
+            // width first page pinned item.
             mFirstPagePinnedItem = LayoutInflater.from(getContext())
                     .inflate(R.layout.search_container_workspace, firstPage, false);
         }
@@ -627,7 +629,7 @@
         // transition animations competing with us changing the scroll when we add pages
         disableLayoutTransitions();
 
-        // Recycle the first page pinned widget
+        // Recycle the first page pinned item
         if (mFirstPagePinnedItem != null) {
             ((ViewGroup) mFirstPagePinnedItem.getParent()).removeView(mFirstPagePinnedItem);
         }
@@ -638,12 +640,14 @@
         mScreenOrder.clear();
         mWorkspaceScreens.clear();
 
+        // Ensure that the first page is always present
+        if (!ENABLE_SMARTSPACE_REMOVAL.get()) {
+            bindAndInitFirstWorkspaceScreen();
+        }
+
         // Remove any deferred refresh callbacks
         mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
 
-        // Ensure that the first page is always present
-        bindAndInitFirstWorkspaceScreen();
-
         // Re-enable the layout transitions
         enableLayoutTransitions();
     }
@@ -802,6 +806,13 @@
         // and we store them as extra empty screens.
         for (int i = 0; i < finalScreens.size(); i++) {
             int screenId = finalScreens.keyAt(i);
+
+            // We don't want to remove the first screen even if it's empty because that's where
+            // first page pinned item would go if it gets turned back on.
+            if (ENABLE_SMARTSPACE_REMOVAL.get() && screenId == FIRST_SCREEN_ID) {
+                continue;
+            }
+
             CellLayout screen = finalScreens.get(screenId);
 
             mWorkspaceScreens.remove(screenId);
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 05ed9ba..ac0e5a4 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -62,8 +62,6 @@
 public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
     private static final String TAG = "WorkProfileManager";
 
-    public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
-
     public static final int STATE_ENABLED = 1;
     public static final int STATE_DISABLED = 2;
     public static final int STATE_TRANSITION = 3;
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 08a2f64..c70e786 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -167,6 +167,10 @@
         return SMARTSPACE_AS_A_WIDGET.get() && WIDGET_ON_FIRST_SCREEN;
     }
 
+    public static final BooleanFlag ENABLE_SMARTSPACE_REMOVAL = getDebugFlag(290799975,
+            "ENABLE_SMARTSPACE_REMOVAL", DISABLED, "Enable SmartSpace removal for "
+            + "home screen");
+
     // TODO(Block 10): Clean up flags
     public static final BooleanFlag ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION = getDebugFlag(270614790,
             "ENABLE_BACK_SWIPE_LAUNCHER_ANIMATION", DISABLED,
@@ -308,6 +312,16 @@
                     + "waiting for SystemUI and then merging the SystemUI progress whenever we "
                     + "start receiving the events");
 
+    // TODO(Block 23): Clean up flags
+    // Aconfig migration complete for ENABLE_GRID_ONLY_OVERVIEW.
+    @VisibleForTesting
+    public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
+            "ENABLE_GRID_ONLY_OVERVIEW", TEAMFOOD,
+            "Enable a grid-only overview without a focused task.");
+    public static boolean enableGridOnlyOverview() {
+        return ENABLE_GRID_ONLY_OVERVIEW.get() || Flags.enableGridOnlyOverview();
+    }
+
     // Aconfig migration complete for ENABLE_OVERVIEW_ICON_MENU.
     @VisibleForTesting
     public static final BooleanFlag ENABLE_OVERVIEW_ICON_MENU = getDebugFlag(257950105,
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 7ce5c20..3330448 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -527,7 +527,7 @@
         }
 
         // Add first page QSB
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN && dataModel.isFirstPagePinnedItemEnabled
                 && !shouldShowFirstPageWidget()) {
             CellLayout firstScreen = mWorkspaceScreens.get(FIRST_SCREEN_ID);
             View qsb = mHomeElementInflater.inflate(R.layout.qsb_preview, firstScreen, false);
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
index bb7248f..1791539 100644
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -107,7 +107,7 @@
         try {
             return context.getSystemService(LauncherApps.class)
                     .getShortcutIconDrawable(shortcutInfo, density);
-        } catch (SecurityException | IllegalStateException e) {
+        } catch (SecurityException | IllegalStateException | NullPointerException e) {
             Log.e(TAG, "Failed to get shortcut icon", e);
             return null;
         }
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 7421205..9b2344d 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -293,6 +294,10 @@
             executeCallbacksTask(c -> {
                 c.clearPendingBinds();
                 c.startBinding();
+                if (ENABLE_SMARTSPACE_REMOVAL.get()) {
+                    c.setIsFirstPagePinnedItemEnabled(
+                            mBgDataModel.isFirstPagePinnedItemEnabled);
+                }
             }, mUiExecutor);
 
             // Bind workspace screens
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 0ffedc8..54ecc00 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -17,6 +17,8 @@
 
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
 
+import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
 import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
@@ -130,6 +132,8 @@
      * Load id for which the callbacks were successfully bound
      */
     public int lastLoadId = -1;
+    public boolean isFirstPagePinnedItemEnabled = QSB_ON_FIRST_SCREEN
+            && !ENABLE_SMARTSPACE_REMOVAL.get();
 
     /**
      * Clears all the data
@@ -489,6 +493,7 @@
 
         default void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
         default void bindScreens(IntArray orderedScreenIds) { }
+        default void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) { }
         default void finishBindingItems(IntSet pagesBoundFirst) { }
         default void preAddApps() { }
         default void bindAppsAdded(IntArray newScreens,
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index eed4ee6..efd5574 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -18,7 +18,9 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.shouldShowFirstPageWidget;
+import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
 import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
@@ -38,6 +40,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -330,6 +333,8 @@
         final Point trg = new Point(trgX, trgY);
         final Point next = new Point(0, screenId == 0
                 && (FeatureFlags.QSB_ON_FIRST_SCREEN
+                && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(destReader.mContext)
+                .getBoolean(SMARTSPACE_ON_HOME_SCREEN, true))
                 && !shouldShowFirstPageWidget())
                 ? 1 /* smartspace */ : 0);
         List<DbEntry> existedEntries = destReader.mWorkspaceEntriesByScreenId.get(screenId);
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 9c4cd46..4370043 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -471,7 +471,7 @@
             // cause the item loading to get skipped
             ShortcutKey.fromItemInfo(info);
         }
-        if (checkItemPlacement(info)) {
+        if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) {
             dataModel.addItem(mContext, info, false, logger);
         } else {
             markDeleted("Item position overlap");
@@ -481,7 +481,7 @@
     /**
      * check & update map of what's occupied; used to discard overlapping/invalid items
      */
-    protected boolean checkItemPlacement(ItemInfo item) {
+    protected boolean checkItemPlacement(ItemInfo item, boolean isFirstPagePinnedItemEnabled) {
         int containerIndex = item.screenId;
         if (item.container == Favorites.CONTAINER_HOTSEAT) {
             final GridOccupancy hotseatOccupancy =
@@ -530,7 +530,7 @@
         if (!mOccupied.containsKey(item.screenId)) {
             GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1);
             if (item.screenId == Workspace.FIRST_SCREEN_ID && (FeatureFlags.QSB_ON_FIRST_SCREEN
-                    && !shouldShowFirstPageWidget())) {
+                    && !shouldShowFirstPageWidget() && isFirstPagePinnedItemEnabled)) {
                 // Mark the first X columns (X is width of the search container) in the first row as
                 // occupied (if the feature is enabled) in order to account for the search
                 // container.
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 15666cf..6ab8fc5 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
 import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
@@ -119,6 +120,7 @@
  */
 public class LoaderTask implements Runnable {
     private static final String TAG = "LoaderTask";
+    public static final String SMARTSPACE_ON_HOME_SCREEN = "pref_smartspace_home_screen";
 
     private static final boolean DEBUG = true;
 
@@ -368,6 +370,9 @@
             mModelDelegate.markActive();
             logASplit("workspaceDelegateItems");
         }
+        mBgDataModel.isFirstPagePinnedItemEnabled = FeatureFlags.QSB_ON_FIRST_SCREEN
+                && (!ENABLE_SMARTSPACE_REMOVAL.get() || LauncherPrefs.getPrefs(
+                mApp.getContext()).getBoolean(SMARTSPACE_ON_HOME_SCREEN, true));
     }
 
     private void loadWorkspaceImpl(
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 7b4e248..6950fb5 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -25,14 +25,13 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
 
-import android.app.Activity;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.os.Handler;
 import android.os.Message;
 
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseActivity;
@@ -62,8 +61,8 @@
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
-    @Nullable
-    private BaseActivity mActivity;
+    @NonNull
+    private final BaseActivity mActivity;
     private final Handler mRequestOrientationHandler;
 
     private boolean mIgnoreAutoRotateSettings;
@@ -92,14 +91,14 @@
     // Initialize mLastActivityFlags to a value not used by SCREEN_ORIENTATION flags
     private int mLastActivityFlags = -999;
 
-    public RotationHelper(BaseActivity activity) {
+    public RotationHelper(@NonNull BaseActivity activity) {
         mActivity = activity;
         mRequestOrientationHandler =
                 new Handler(UI_HELPER_EXECUTOR.getLooper(), this::setOrientationAsync);
     }
 
-    private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings,
-            DisplayController.Info info) {
+    private void setIgnoreAutoRotateSettings(boolean ignoreAutoRotateSettings) {
+        if (mDestroyed) return;
         // On large devices we do not handle auto-rotate differently.
         mIgnoreAutoRotateSettings = ignoreAutoRotateSettings;
         if (!mIgnoreAutoRotateSettings) {
@@ -122,58 +121,54 @@
 
     @Override
     public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+        if (mDestroyed) return;
         boolean ignoreAutoRotateSettings = info.isTablet(info.realBounds);
         if (mIgnoreAutoRotateSettings != ignoreAutoRotateSettings) {
-            setIgnoreAutoRotateSettings(ignoreAutoRotateSettings, info);
+            setIgnoreAutoRotateSettings(ignoreAutoRotateSettings);
             notifyChange();
         }
     }
 
     public void setStateHandlerRequest(int request) {
-        if (mStateHandlerRequest != request) {
-            mStateHandlerRequest = request;
-            notifyChange();
-        }
+        if (mDestroyed || mStateHandlerRequest == request) return;
+        mStateHandlerRequest = request;
+        notifyChange();
     }
 
     public void setCurrentTransitionRequest(int request) {
-        if (mCurrentTransitionRequest != request) {
-            mCurrentTransitionRequest = request;
-            notifyChange();
-        }
+        if (mDestroyed || mCurrentTransitionRequest == request) return;
+        mCurrentTransitionRequest = request;
+        notifyChange();
     }
 
     public void setCurrentStateRequest(int request) {
-        if (mCurrentStateRequest != request) {
-            mCurrentStateRequest = request;
-            notifyChange();
-        }
+        if (mDestroyed || mCurrentStateRequest == request) return;
+        mCurrentStateRequest = request;
+        notifyChange();
     }
 
     // Used by tests only.
     public void forceAllowRotationForTesting(boolean allowRotation) {
+        if (mDestroyed) return;
         mForceAllowRotationForTesting = allowRotation;
         notifyChange();
     }
 
     public void initialize() {
-        if (!mInitialized) {
-            mInitialized = true;
-            DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
-            DisplayController.Info info = displayController.getInfo();
-            setIgnoreAutoRotateSettings(info.isTablet(info.realBounds), info);
-            displayController.addChangeListener(this);
-            notifyChange();
-        }
+        if (mInitialized) return;
+        mInitialized = true;
+        DisplayController displayController = DisplayController.INSTANCE.get(mActivity);
+        DisplayController.Info info = displayController.getInfo();
+        setIgnoreAutoRotateSettings(info.isTablet(info.realBounds));
+        displayController.addChangeListener(this);
+        notifyChange();
     }
 
     public void destroy() {
-        if (!mDestroyed) {
-            mDestroyed = true;
-            DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
-            LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
-            mActivity = null;
-        }
+        if (mDestroyed) return;
+        mDestroyed = true;
+        DisplayController.INSTANCE.get(mActivity).removeChangeListener(this);
+        LauncherPrefs.get(mActivity).removeListener(this, ALLOW_ROTATION);
     }
 
     private void notifyChange() {
@@ -206,10 +201,8 @@
 
     @WorkerThread
     private boolean setOrientationAsync(Message msg) {
-        Activity activity = mActivity;
-        if (activity != null) {
-            activity.setRequestedOrientation(msg.what);
-        }
+        if (mDestroyed) return true;
+        mActivity.setRequestedOrientation(msg.what);
         return true;
     }
 
@@ -228,8 +221,10 @@
     public String toString() {
         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d, "
                         + "mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, "
-                        + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b]",
+                        + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b,"
+                        + " mDestroyed=%b]",
                 mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
-                mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting);
+                mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting,
+                mDestroyed);
     }
 }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index e52e878..a75f326 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.testing;
 
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.config.FeatureFlags.enableGridOnlyOverview;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 122b1e0..a09e5a4 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
@@ -136,6 +137,9 @@
         if (launcher.isWorkspaceLocked()) return false;
         // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
         if (launcher.getDragController().isDragging()) return false;
+        // Return early if user is in the middle of selecting split-screen apps
+        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() &&
+                launcher.isSplitSelectionEnabled()) return false;
 
         return true;
     }
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 96ae4a3..1232069 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.testing.TestLogging;
@@ -205,6 +206,10 @@
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
                 mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
+                if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() &&
+                        mLauncher.isSplitSelectionEnabled()) {
+                    mLauncher.dismissSplitSelection();
+                }
             } else {
                 cancelLongPress();
             }
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 04f2ffa..0d55f15 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -146,6 +146,15 @@
     }
 
     /**
+     * @return {@code true} if user has selected the first split app and is in the process of
+     *         selecting the second
+     */
+    default boolean isSplitSelectionEnabled() {
+        // Overridden
+        return false;
+    }
+
+    /**
      * The root view to support drag-and-drop and popup support.
      */
     BaseDragLayer getDragLayer();
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 742d2dc..fc9c774 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.widget;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_TIP_SEEN;
 
 import android.content.Context;
 import android.graphics.Canvas;
@@ -40,6 +41,7 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -63,8 +65,6 @@
     /** The default number of cells that can fit horizontally in a widget sheet. */
     public static final int DEFAULT_MAX_HORIZONTAL_SPANS = 4;
 
-    protected static final String KEY_WIDGETS_EDUCATION_TIP_SEEN =
-            "launcher.widgets_education_tip_seen";
     protected final Rect mInsets = new Rect();
 
     /* Touch handling related member variables. */
@@ -330,15 +330,14 @@
                         /* arrowXCoord= */coords[0] + view.getWidth() / 2,
                         /* yCoord= */coords[1]);
         if (arrowTipView != null) {
-            mActivityContext.getSharedPrefs().edit()
-                    .putBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, true).apply();
+            LauncherPrefs.get(getContext()).put(WIDGETS_EDUCATION_TIP_SEEN, true);
         }
         return arrowTipView;
     }
 
     /** Returns {@code true} if tip has previously been shown on any of {@link BaseWidgetSheet}. */
     protected boolean hasSeenEducationTip() {
-        return mActivityContext.getSharedPrefs().getBoolean(KEY_WIDGETS_EDUCATION_TIP_SEEN, false)
+        return LauncherPrefs.get(getContext()).get(WIDGETS_EDUCATION_TIP_SEEN)
                 || Utilities.isRunningInTestHarness();
     }
 
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 37ec65c..78116ae 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -18,6 +18,7 @@
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
+import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_DIALOG_SEEN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
@@ -56,6 +57,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
@@ -98,8 +100,6 @@
     // resolution or landscape on phone. This ratio defines the max percentage of content area that
     // the table can display.
     private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
-    private static final String KEY_WIDGETS_EDUCATION_DIALOG_SEEN =
-            "launcher.widgets_education_dialog_seen";
 
     private final UserManagerState mUserManagerState = new UserManagerState();
     private final UserHandle mCurrentUser = Process.myUserHandle();
@@ -853,15 +853,13 @@
 
     /** Shows education dialog for widgets. */
     private WidgetsEduView showEducationDialog() {
-        mActivityContext.getSharedPrefs().edit()
-                .putBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, true).apply();
+        LauncherPrefs.get(getContext()).put(WIDGETS_EDUCATION_DIALOG_SEEN, true);
         return WidgetsEduView.showEducationDialog(mActivityContext);
     }
 
     /** Returns {@code true} if education dialog has previously been shown. */
     protected boolean hasSeenEducationDialog() {
-        return mActivityContext.getSharedPrefs()
-                .getBoolean(KEY_WIDGETS_EDUCATION_DIALOG_SEEN, false)
+        return LauncherPrefs.get(getContext()).get(WIDGETS_EDUCATION_DIALOG_SEEN)
                 || Utilities.isRunningInTestHarness();
     }
 
diff --git a/src_plugins/com/android/systemui/plugins/LauncherOverlayPlugin.java b/src_plugins/com/android/systemui/plugins/LauncherOverlayPlugin.java
index 9e22355..32f0216 100644
--- a/src_plugins/com/android/systemui/plugins/LauncherOverlayPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/LauncherOverlayPlugin.java
@@ -18,7 +18,6 @@
 import android.app.Activity;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
-import com.android.systemui.plugins.shared.LauncherExterns;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 
 /**
@@ -29,6 +28,6 @@
     String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERLAY";
     int VERSION = 1;
 
-    LauncherOverlayManager createOverlayManager(Activity activity, LauncherExterns externs);
+    LauncherOverlayManager createOverlayManager(Activity activity);
 
 }
diff --git a/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java b/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
deleted file mode 100644
index 173b454..0000000
--- a/src_plugins/com/android/systemui/plugins/shared/LauncherExterns.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.plugins.shared;
-
-import android.content.SharedPreferences;
-
-import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
-
-/**
- * This interface defines the set of methods that the Launcher activity exposes. Methods
- * here should be safe to call from classes outside of com.android.launcher3.*
- */
-public interface LauncherExterns {
-
-    /**
-     * Returns the shared main preference
-     */
-    SharedPreferences getSharedPrefs();
-
-    /**
-     * Returns the device specific preference
-     */
-    SharedPreferences getDevicePrefs();
-
-    /**
-     * Sets the overlay on the target activity
-     */
-    void setLauncherOverlay(LauncherOverlay overlay);
-}
diff --git a/tests/Android.bp b/tests/Android.bp
index c4bd7cf..da447b3 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -90,6 +90,7 @@
         "androidx.test.espresso.contrib",
         "androidx.test.espresso.intents",
         "androidx.test.uiautomator_uiautomator",
+        "mockito-kotlin2",
         "mockito-target-extended-minus-junit4",
         "launcher_log_protos_lite",
         "truth-prebuilt",
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index a52ba9e..84fa988 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -38,10 +38,10 @@
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
-import org.mockito.ArgumentMatchers
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 /**
  * This is an abstract class for DeviceProfile tests that create an InvariantDeviceProfile based on
@@ -52,8 +52,8 @@
 abstract class AbstractDeviceProfileTest {
     protected var context: Context? = null
     protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
-    private var displayController: DisplayController = mock(DisplayController::class.java)
-    private var windowManagerProxy: WindowManagerProxy = mock(WindowManagerProxy::class.java)
+    private val displayController: DisplayController = mock()
+    private val windowManagerProxy: WindowManagerProxy = mock()
     private lateinit var originalDisplayController: DisplayController
     private lateinit var originalWindowManagerProxy: WindowManagerProxy
 
@@ -288,11 +288,10 @@
     ) {
         val windowsBounds = perDisplayBoundsCache[displayInfo]!!
         val realBounds = windowsBounds[rotation]
-        whenever(windowManagerProxy.getDisplayInfo(ArgumentMatchers.any())).thenReturn(displayInfo)
-        whenever(windowManagerProxy.getRealBounds(ArgumentMatchers.any(), ArgumentMatchers.any()))
-            .thenReturn(realBounds)
-        whenever(windowManagerProxy.getRotation(ArgumentMatchers.any())).thenReturn(rotation)
-        whenever(windowManagerProxy.getNavigationMode(ArgumentMatchers.any()))
+        whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
+        whenever(windowManagerProxy.getRealBounds(any(), any())).thenReturn(realBounds)
+        whenever(windowManagerProxy.getRotation(any())).thenReturn(rotation)
+        whenever(windowManagerProxy.getNavigationMode(any()))
             .thenReturn(
                 if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
             )
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 42338bf..a421006 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -27,9 +27,9 @@
 import java.io.PrintWriter
 import java.io.StringWriter
 import org.junit.Before
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 /**
  * This is an abstract class for DeviceProfile tests that don't need the real Context and mock an
@@ -41,7 +41,7 @@
 
     protected var context: Context? = null
     protected var inv: InvariantDeviceProfile? = null
-    protected var info: Info = mock(Info::class.java)
+    protected val info: Info = mock()
     protected var windowBounds: WindowBounds? = null
     protected var isMultiWindowMode: Boolean = false
     protected var transposeLayoutWithOrientation: Boolean = false
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
index 712806c..5b87a05 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
@@ -45,7 +45,7 @@
 
 /**
  * Test runs in Out of process (Oop) and In process (Ipc)
- * Test the behaviour of uninstalling and removing apps both from AllApps and from the Workspace.
+ * Test the behaviour of uninstalling and removing apps both from AllApps, Workspace and Hotseat.
  */
 public class TaplUninstallRemove extends AbstractLauncherUiTest {
 
@@ -164,4 +164,19 @@
             TestUtil.uninstallDummyApp();
         }
     }
+
+    /**
+     * Drag icon from the Hotseat to the delete drop target
+     */
+    @Test
+    @PortraitLandscape
+    public void testAddDeleteShortcutOnHotseat() {
+        mLauncher.getWorkspace()
+                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
+                .switchToAllApps()
+                .getAppIcon(APP_NAME)
+                .dragToHotseat(0);
+        mLauncher.getWorkspace().deleteAppIcon(
+                mLauncher.getWorkspace().getHotseatAppIcon(APP_NAME));
+    }
 }
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index 1155227..78c61d5 100644
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -24,20 +24,19 @@
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.IntArray
 import com.android.launcher3.util.TestUtil.runOnExecutorSync
-import com.android.launcher3.util.any
-import com.android.launcher3.util.eq
-import com.android.launcher3.util.same
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
 import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
 
 /** Tests for [AddWorkspaceItemsTask] */
 @SmallTest
@@ -46,12 +45,11 @@
 
     private lateinit var mDataModelCallbacks: MyCallbacks
 
-    @Mock private lateinit var mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder
+    private val mWorkspaceItemSpaceFinder: WorkspaceItemSpaceFinder = mock()
 
     @Before
     override fun setup() {
         super.setup()
-        MockitoAnnotations.initMocks(this)
         mDataModelCallbacks = MyCallbacks()
         Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(mDataModelCallbacks) }
             .get()
diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 544ed6b..389ec5c 100644
--- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -175,7 +175,7 @@
 
         // Item outside screen bounds are not placed
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1)));
+                newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1), true));
     }
 
     @Test
@@ -186,22 +186,22 @@
 
         // Overlapping mItems are not placed
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), true));
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), true));
 
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2)));
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), true));
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2)));
+                newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), true));
 
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1)));
+                newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1), true));
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1)));
+                newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1), true));
 
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1)));
+                newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1), true));
     }
 
     @Test
@@ -212,12 +212,12 @@
 
         // Hotseat mItems are only placed based on screenId
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1)));
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1), true));
         assertTrue(mLoaderCursor.checkItemPlacement(
-                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2)));
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2), true));
 
         assertFalse(mLoaderCursor.checkItemPlacement(
-                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3)));
+                newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), true));
     }
 
     private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index e837b8b..fe51509 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -183,8 +183,8 @@
         if (TestHelpers.isInLauncherProcess()) {
             Utilities.enableRunningInTestHarnessForTests();
             mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
-                    TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString()).
-                    getString("result"));
+                            TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString())
+                            .getString("result"));
             mLauncher.setOnSettledStateAction(
                     containerType -> executeOnLauncher(
                             launcher ->
@@ -207,7 +207,7 @@
         final SimpleBroadcastReceiver broadcastReceiver =
                 new SimpleBroadcastReceiver(i -> count.countDown());
         broadcastReceiver.registerPkgActions(mTargetContext, pkg,
-                        Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
+                Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
 
         mDevice.executeShellCommand("pm clear " + pkg);
         assertTrue(pkg + " didn't restart", count.await(10, TimeUnit.SECONDS));
@@ -344,6 +344,15 @@
         });
     }
 
+    // Execute an action on Launcher, but forgive it when launcher is null.
+    // Launcher can be null if teardown is happening after a failed setup step where launcher
+    // activity failed to be created.
+    protected void executeOnLauncherInTearDown(Consumer<Launcher> f) {
+        executeOnLauncher(launcher -> {
+            if (launcher != null) f.accept(launcher);
+        });
+    }
+
     // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
     // expecting the results of that gesture because the wait can hide flakeness.
     protected void waitForState(String message, Supplier<LauncherState> state) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index f37b676..799ef5b 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -82,16 +82,4 @@
         // Check that pressHome works when the menu is shown.
         mLauncher.goHome();
     }
-
-    @Test
-    @PortraitLandscape
-    public void testAddDeleteShortcutOnHotseat() {
-        mLauncher.getWorkspace()
-                .deleteAppIcon(mLauncher.getWorkspace().getHotseatAppIcon(0))
-                .switchToAllApps()
-                .getAppIcon(APP_NAME)
-                .dragToHotseat(0);
-        mLauncher.getWorkspace().deleteAppIcon(
-                mLauncher.getWorkspace().getHotseatAppIcon(APP_NAME));
-    }
 }
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index ac710fd..485ef94 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.ui;
 
+import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
@@ -29,6 +30,7 @@
 
 import androidx.recyclerview.widget.RecyclerView.ViewHolder;
 
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsPagedView;
@@ -89,8 +91,8 @@
 
     @After
     public void removeWorkProfile() throws Exception {
-        executeOnLauncher(launcher -> {
-            if (launcher == null || launcher.getAppsView() == null) {
+        executeOnLauncherInTearDown(launcher -> {
+            if (launcher.getAppsView() == null) {
                 return;
             }
             launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
@@ -174,7 +176,7 @@
         assumeTrue(mWorkProfileSetupSuccessful);
         waitForWorkTabSetup();
         executeOnLauncher(l -> {
-            l.getSharedPrefs().edit().putInt(WorkProfileManager.KEY_WORK_EDU_STEP, 0).commit();
+            LauncherPrefs.get(l).putSync(WORK_EDU_STEP.to(0));
             ((AllAppsPagedView) l.getAppsView().getContentView()).setCurrentPage(WORK_PAGE);
             l.getAppsView().getWorkManager().reset();
         });
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
index 62a8179..00e00fa 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -83,7 +83,7 @@
 
     @After
     public void tearDown() throws Exception {
-        executeOnLauncher(launcher -> launcher.enableHotseatEdu(true));
+        executeOnLauncherInTearDown(launcher -> launcher.enableHotseatEdu(true));
         if (mLauncherLayout != null) {
             mLauncherLayout.close();
         }
diff --git a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
index a94dd2e..8670d40 100644
--- a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -42,11 +42,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.doNothing
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 import org.mockito.stubbing.Answer
 
 /** Unit tests for {@link DisplayController} */
@@ -56,13 +58,13 @@
 
     private val appContext: Context = ApplicationProvider.getApplicationContext()
 
-    @Mock private lateinit var context: SandboxContext
-    @Mock private lateinit var windowManagerProxy: WindowManagerProxy
-    @Mock private lateinit var launcherPrefs: LauncherPrefs
-    @Mock private lateinit var displayManager: DisplayManager
-    @Mock private lateinit var display: Display
-    @Mock private lateinit var resources: Resources
-    @Mock private lateinit var displayInfoChangeListener: DisplayInfoChangeListener
+    private val context: SandboxContext = mock()
+    private val windowManagerProxy: WindowManagerProxy = mock()
+    private val launcherPrefs: LauncherPrefs = mock()
+    private val displayManager: DisplayManager = mock()
+    private val display: Display = mock()
+    private val resources: Resources = mock()
+    private val displayInfoChangeListener: DisplayInfoChangeListener = mock()
 
     private lateinit var displayController: DisplayController
 
@@ -88,7 +90,6 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
         whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
         whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
@@ -112,10 +113,10 @@
 
         whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
         // Mock context
-        whenever(context.createWindowContext(any(), any(), nullable())).thenReturn(context)
+        whenever(context.createWindowContext(any(), any(), anyOrNull())).thenReturn(context)
         whenever(context.getSystemService(eq(DisplayManager::class.java)))
             .thenReturn(displayManager)
-        doNothing().`when`(context).registerComponentCallbacks(any())
+        doNothing().whenever(context).registerComponentCallbacks(any())
 
         // Mock display
         whenever(display.rotation).thenReturn(displayInfo.rotation)
diff --git a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt b/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
deleted file mode 100644
index c9c9616..0000000
--- a/tests/src/com/android/launcher3/util/KotlinMockitoHelpers.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util
-
-/**
- * Kotlin versions of popular mockito methods that can return null in situations when Kotlin expects
- * a non-null value. Kotlin will throw an IllegalStateException when this takes place ("x must not
- * be null"). To fix this, we can use methods that modify the return type to be nullable. This
- * causes Kotlin to skip the null checks.
- */
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito
-
-/**
- * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
-
-/**
- * Returns Mockito.same() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> same(obj: T): T = Mockito.same<T>(obj)
-
-/**
- * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when null is
- * returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
-
-inline fun <reified T> any(): T = any(T::class.java)
-
-/** Kotlin type-inferred version of Mockito.nullable() */
-inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
-
-/**
- * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException when
- * null is returned.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-
-/**
- * Helper function for creating an argumentCaptor in kotlin.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> =
-    ArgumentCaptor.forClass(T::class.java)
-
-/**
- * Helper function for creating new mocks, without the need to pass in a [Class] instance.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> mock(): T = Mockito.mock(T::class.java)
-
-/**
- * A kotlin implemented wrapper of [ArgumentCaptor] which prevents the following exception when
- * kotlin tests are mocking kotlin objects and the methods take non-null parameters:
- * ```
- *     java.lang.NullPointerException: capture() must not be null
- * ```
- */
-class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) {
-    private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz)
-    fun capture(): T = wrapped.capture()
-    val value: T
-        get() = wrapped.value
-}
-
-/**
- * Helper function for creating an argumentCaptor in kotlin.
- *
- * Generic T is nullable because implicitly bounded by Any?.
- */
-inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> =
-    KotlinArgumentCaptor(T::class.java)
-
-/**
- * Helper function for creating and using a single-use ArgumentCaptor in kotlin.
- *
- * ```
- *    val captor = argumentCaptor<Foo>()
- *    verify(...).someMethod(captor.capture())
- *    val captured = captor.value
- * ```
- *
- * becomes:
- * ```
- *    val captured = withArgCaptor<Foo> { verify(...).someMethod(capture()) }
- * ```
- *
- * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
- */
-inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
-    kotlinArgumentCaptor<T>().apply { block() }.value
diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 92ab2cb..2c4a54f 100644
--- a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -26,29 +26,27 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when`
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.whenever
 
 /** Unit tests for {@link LockedUserState} */
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class LockedUserStateTest {
 
-    @Mock lateinit var userManager: UserManager
-    @Mock lateinit var context: Context
+    private val userManager: UserManager = mock()
+    private val context: Context = mock()
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-        `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
+        whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
     }
 
     @Test
     fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+        whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
         val action: Runnable = mock()
         LockedUserState(context).runOnUserUnlocked(action)
         verify(action).run()
@@ -56,7 +54,7 @@
 
     @Test
     fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+        whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
         val action: Runnable = mock()
         val state = LockedUserState(context)
         state.runOnUserUnlocked(action)
@@ -67,13 +65,13 @@
 
     @Test
     fun isUserUnlocked_returns_true_when_user_is_unlocked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+        whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
         assertThat(LockedUserState(context).isUserUnlocked).isTrue()
     }
 
     @Test
     fun isUserUnlocked_returns_false_when_user_is_locked() {
-        `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+        whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
         assertThat(LockedUserState(context).isUserUnlocked).isFalse()
     }
 }
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
index 4b65439..51b7b18 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -62,18 +62,6 @@
                     + "NexusOverviewActionsView:id/overview_actions_view|FrameLayout:id"
                     + "/select_mode_buttons|ImageButton:id/close",
             DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
-                    + "/action_buttons|Button:id/action_screenshot",
-            DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
-                    + "/action_buttons|Button:id/action_select",
-            DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
-                    + "/action_buttons|Button:id/action_split",
-            DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
-                    + "/action_buttons|Space:id/action_split_space",
-            DRAG_LAYER
                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
                     + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material"
                     + "|DeepShortcutTextView:id/bubble_text",
@@ -116,23 +104,14 @@
             RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
             DRAG_LAYER
                     + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
+                    + "|LinearLayout:id/action_buttons",
             RECENTS_DRAG_LAYER
                     + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
+                    + "|LinearLayout:id/action_buttons",
+            DRAG_LAYER + "IconView",
             DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_select",
-            RECENTS_DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_select",
-            DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_split",
-            RECENTS_DRAG_LAYER
-                    + "NexusOverviewActionsView:id/overview_actions_view"
-                    + "|LinearLayout:id/action_buttons|Button:id/action_split",
-            DRAG_LAYER + "IconView"
+                    + "OptionsPopupView:id/popup_container|DeepShortcutView:id/system_shortcut"
+                    + "|BubbleTextView:id/bubble_text"
     ));
 
     // Minimal increase or decrease of view's alpha between frames that triggers the error.
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
index 0d4eaf1..e333074 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
@@ -38,7 +38,8 @@
 
     private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of(
             CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
-            DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView|TextView",
+            DRAG_LAYER + "LauncherRecentsView:id/overview_panel|TaskView",
+            DRAG_LAYER + "LauncherRecentsView:id/overview_panel|ClearAllButton:id/clear_all",
             DRAG_LAYER
                     + "LauncherAllAppsContainerView:id/apps_view|AllAppsRecyclerView:id"
                     + "/apps_list_view|BubbleTextView:id/icon",
@@ -54,6 +55,8 @@
                     + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
                     + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge",
             RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView",
+            RECENTS_DRAG_LAYER
+                    + "FallbackRecentsView:id/overview_panel|ClearAllButton:id/clear_all",
             DRAG_LAYER + "SearchContainerView:id/apps_view",
             DRAG_LAYER + "LauncherDragView",
             DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail",
@@ -64,7 +67,8 @@
                     + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container|LinearLayout:id"
                     + "/linear_layout_container|FrameLayout:id/recycler_view_container"
                     + "|FrameLayout:id/widgets_two_pane_sheet_recyclerview|WidgetsRecyclerView:id"
-                    + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header"
+                    + "/primary_widgets_list_view|WidgetsListHeader:id/widgets_list_header",
+            DRAG_LAYER + "NexusOverviewActionsView:id/overview_actions_view"
     ));
 
     // Per-AnalysisNode data that's specific to this detector.
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index fc589bd..2a2a83f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -28,7 +28,6 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.SystemClock;
-import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
@@ -302,10 +301,6 @@
         final UiObject2 workspace = verifyActiveContainer();
         List<UiObject2> workspaceIcons =
                 mLauncher.waitForObjectsInContainer(workspace, AppIcon.getAnyAppIconSelector());
-        Log.d("b/288944469", "List size = " + workspaceIcons.size());
-        for (int i = 0; i < workspaceIcons.size(); i++) {
-            Log.d("b/288944469", "index = " + i + " tesxt = " + workspaceIcons.get(i).getText());
-        }
         return workspaceIcons.stream()
                 .collect(
                         Collectors.toMap(