Merge "Align ClearAllButton to centre when the ENABLE_GRID_ONLY_OVERVIEW is enabled" into udc-qpr-dev
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 63f192c..1b5b0ee 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -25,9 +25,17 @@
 
     <!-- Taskbar -->
     <color name="taskbar_nav_icon_selection_ripple">#E0E0E0</color>
+    <color name="taskbar_nav_icon_light_color_on_home">#ffffff</color>
+    <!-- The dark navigation button color is only used in the rare cases that taskbar isn't drawing
+    its background and the underlying app has requested dark buttons. -->
+    <color name="taskbar_nav_icon_dark_color_on_home">#99000000</color>
     <color name="taskbar_stashed_handle_light_color">#EBffffff</color>
     <color name="taskbar_stashed_handle_dark_color">#99000000</color>
 
+    <!-- Floating rotation button -->
+    <color name="floating_rotation_button_light_color">#ffffff</color>
+    <color name="floating_rotation_button_dark_color">#99000000</color>
+
     <!-- Gesture navigation tutorial -->
     <color name="gesture_tutorial_back_arrow_color">#FFFFFFFF</color>
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index ba6f165..35b9957 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -312,6 +312,8 @@
                     .getTaskbarNavButtonTranslationYForInAppDisplay()
                     .updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
                             * mTaskbarInAppDisplayProgress.value);
+            mControllers.navbarButtonsViewController
+                    .getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 9a9e0ba..10ae97b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -148,8 +148,8 @@
     // Used for IME+A11Y buttons
     private final ViewGroup mEndContextualContainer;
     private final ViewGroup mStartContextualContainer;
-    private final int mLightIconColor;
-    private final int mDarkIconColor;
+    private final int mLightIconColorOnHome;
+    private final int mDarkIconColorOnHome;
     /** Color to use for navigation bar buttons, if they are on on a Taskbar surface background. */
     private final int mOnBackgroundIconColor;
 
@@ -205,9 +205,11 @@
         mEndContextualContainer = mNavButtonsView.findViewById(R.id.end_contextual_buttons);
         mStartContextualContainer = mNavButtonsView.findViewById(R.id.start_contextual_buttons);
 
-        mLightIconColor = context.getColor(R.color.taskbar_nav_icon_light_color);
-        mDarkIconColor = context.getColor(R.color.taskbar_nav_icon_dark_color);
-        mOnBackgroundIconColor = Utilities.isDarkTheme(context) ? mLightIconColor : mDarkIconColor;
+        mLightIconColorOnHome = context.getColor(R.color.taskbar_nav_icon_light_color_on_home);
+        mDarkIconColorOnHome = context.getColor(R.color.taskbar_nav_icon_dark_color_on_home);
+        mOnBackgroundIconColor = Utilities.isDarkTheme(context)
+                ? context.getColor(R.color.taskbar_nav_icon_light_color)
+                : context.getColor(R.color.taskbar_nav_icon_dark_color);
     }
 
     /**
@@ -630,18 +632,20 @@
 
     private void updateNavButtonColor() {
         final ArgbEvaluator argbEvaluator = ArgbEvaluator.getInstance();
-        final int sysUiNavButtonIconColor = (int) argbEvaluator.evaluate(
+        final int sysUiNavButtonIconColorOnHome = (int) argbEvaluator.evaluate(
                 mTaskbarNavButtonDarkIntensity.value,
-                mLightIconColor,
-                mDarkIconColor);
+                mLightIconColorOnHome,
+                mDarkIconColorOnHome);
+
         // Override the color from framework if nav buttons are over an opaque Taskbar surface.
         final int iconColor = (int) argbEvaluator.evaluate(
                 mOnBackgroundNavButtonColorOverrideMultiplier.value
                         * Math.max(
                                 mOnTaskbarBackgroundNavButtonColorOverride.value,
                                 mSlideInViewVisibleNavButtonColorOverride.value),
-                sysUiNavButtonIconColor,
+                sysUiNavButtonIconColorOnHome,
                 mOnBackgroundIconColor);
+
         for (ImageView button : mAllButtons) {
             button.setImageTintList(ColorStateList.valueOf(iconColor));
         }
@@ -928,8 +932,6 @@
         pw.println(prefix + "NavbarButtonsViewController:");
 
         pw.println(prefix + "\tmState=" + getStateString(mState));
-        pw.println(prefix + "\tmLightIconColor=" + Integer.toHexString(mLightIconColor));
-        pw.println(prefix + "\tmDarkIconColor=" + Integer.toHexString(mDarkIconColor));
         pw.println(prefix + "\tmFloatingRotationButtonBounds=" + mFloatingRotationButtonBounds);
         pw.println(prefix + "\tmSysuiStateFlags=" + QuickStepContract.getSystemUiStateString(
                 mSysuiStateFlags));
@@ -940,6 +942,14 @@
                 + mTaskbarNavButtonTranslationYForInAppDisplay.value);
         pw.println(prefix + "\t\tmTaskbarNavButtonTranslationYForIme="
                 + mTaskbarNavButtonTranslationYForIme.value);
+        pw.println(prefix + "\t\tmTaskbarNavButtonDarkIntensity="
+                + mTaskbarNavButtonDarkIntensity.value);
+        pw.println(prefix + "\t\tmSlideInViewVisibleNavButtonColorOverride="
+                + mSlideInViewVisibleNavButtonColorOverride.value);
+        pw.println(prefix + "\t\tmOnTaskbarBackgroundNavButtonColorOverride="
+                + mOnTaskbarBackgroundNavButtonColorOverride.value);
+        pw.println(prefix + "\t\tmOnBackgroundNavButtonColorOverrideMultiplier="
+                + mOnBackgroundNavButtonColorOverrideMultiplier.value);
     }
 
     private static String getStateString(int flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6fddd4b..31af1ce 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -49,7 +49,6 @@
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.provider.Settings;
 import android.util.Log;
@@ -128,8 +127,6 @@
 
     private static final String IME_DRAWS_IME_NAV_BAR_RES_NAME = "config_imeDrawsImeNavBar";
 
-    private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
-            SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
     private static final String TAG = "TaskbarActivityContext";
 
     private static final String WINDOW_TITLE = "Taskbar";
@@ -169,30 +166,27 @@
             TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
             unfoldTransitionProgressProvider) {
         super(windowContext);
+
+        applyDeviceProfile(launcherDp);
+
         final Resources resources = getResources();
 
-        matchDeviceProfile(launcherDp, getResources());
-
-        mNavMode = DisplayController.getNavigationMode(windowContext);
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
+
+        // TODO(b/244231596) For shared Taskbar window, update this value in applyDeviceProfile()
+        //  instead so to get correct value when recreating the taskbar
         SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
         mIsUserSetupComplete = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
-        mIsNavBarForceVisible = settingsCache.getValue(
-                Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
-
-        // TODO(b/244231596) For shared Taskbar window, update this value in init() instead so
-        //  to get correct value when recreating the taskbar
         mIsNavBarKidsMode = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
+        mIsNavBarForceVisible = mIsNavBarKidsMode;
 
         // Get display and corners first, as views might use them in constructor.
         Display display = windowContext.getDisplay();
-        Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
-                ? windowContext.getApplicationContext()
-                : windowContext.getApplicationContext().createDisplayContext(display);
+        Context c = getApplicationContext();
         mWindowManager = c.getSystemService(WindowManager.class);
         mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
         mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
@@ -232,8 +226,8 @@
                         ? new DesktopNavbarButtonsViewController(this, navButtonsView)
                         : new NavbarButtonsViewController(this, navButtonsView),
                 new RotationButtonController(this,
-                        c.getColor(R.color.taskbar_nav_icon_light_color),
-                        c.getColor(R.color.taskbar_nav_icon_dark_color),
+                        c.getColor(R.color.floating_rotation_button_light_color),
+                        c.getColor(R.color.floating_rotation_button_dark_color),
                         R.drawable.ic_sysbar_rotate_button_ccw_start_0,
                         R.drawable.ic_sysbar_rotate_button_ccw_start_90,
                         R.drawable.ic_sysbar_rotate_button_cw_start_0,
@@ -267,6 +261,38 @@
                 bubbleControllersOptional);
     }
 
+    /** Updates {@link DeviceProfile} instances for any Taskbar windows. */
+    public void updateDeviceProfile(DeviceProfile launcherDp) {
+        applyDeviceProfile(launcherDp);
+
+        mControllers.taskbarOverlayController.updateLauncherDeviceProfile(launcherDp);
+        AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
+        // Reapply fullscreen to take potential new screen size into account.
+        setTaskbarWindowFullscreen(mIsFullscreen);
+
+        dispatchDeviceProfileChanged();
+    }
+
+    /**
+     * Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
+     * the icon size
+     */
+    private void applyDeviceProfile(DeviceProfile originDeviceProfile) {
+        mDeviceProfile = originDeviceProfile.toBuilder(this)
+                .withDimensionsOverride(deviceProfile -> {
+                    // Taskbar should match the number of icons of hotseat
+                    deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
+                    // Same QSB width to have a smooth animation
+                    deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
+
+                    // Update icon size
+                    deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
+                    deviceProfile.updateIconSize(1f, getResources());
+                }).build();
+        mNavMode = DisplayController.getNavigationMode(this);
+    }
+
+
     public void init(@NonNull TaskbarSharedState sharedState) {
         mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
         mWindowLayoutParams =
@@ -308,19 +334,6 @@
         return mDeviceProfile;
     }
 
-    /** Updates {@link DeviceProfile} instances for any Taskbar windows. */
-    public void updateDeviceProfile(DeviceProfile launcherDp, NavigationMode navMode) {
-        mNavMode = navMode;
-        mControllers.taskbarOverlayController.updateLauncherDeviceProfile(launcherDp);
-        matchDeviceProfile(launcherDp, getResources());
-
-        AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
-        // Reapply fullscreen to take potential new screen size into account.
-        setTaskbarWindowFullscreen(mIsFullscreen);
-
-        dispatchDeviceProfileChanged();
-    }
-
     @Override
     public void dispatchDeviceProfileChanged() {
         super.dispatchDeviceProfileChanged();
@@ -329,24 +342,6 @@
     }
 
     /**
-     * Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
-     * the icon size
-     */
-    private void matchDeviceProfile(DeviceProfile originDeviceProfile, Resources resources) {
-        mDeviceProfile = originDeviceProfile.toBuilder(this)
-                .withDimensionsOverride(deviceProfile -> {
-                    // Taskbar should match the number of icons of hotseat
-                    deviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
-                    // Same QSB width to have a smooth animation
-                    deviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
-
-                    // Update icon size
-                    deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
-                    deviceProfile.updateIconSize(1f, resources);
-                }).build();
-    }
-
-    /**
      * Returns the View bounds of transient taskbar.
      */
     public Rect getTransientTaskbarBounds() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 7681fe0..f6de926 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -22,6 +22,7 @@
 import android.graphics.Canvas;
 import android.graphics.RectF;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
@@ -33,6 +34,8 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.views.BaseDragLayer;
 
 /**
@@ -40,6 +43,25 @@
  */
 public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
 
+    private static final int INDEX_ALL_OTHER_STATES = 0;
+    private static final int INDEX_STASH_ANIM = 1;
+    private static final int INDEX_COUNT = 2;
+
+    private static final FloatProperty<TaskbarDragLayer> BG_ALPHA =
+            new FloatProperty<>("taskbarBgAlpha") {
+                @Override
+                public void setValue(TaskbarDragLayer dragLayer, float alpha) {
+                    dragLayer.mBackgroundRenderer.getPaint().setAlpha((int) (alpha * 255));
+                    dragLayer.invalidate();
+                }
+
+                @Override
+                public Float get(TaskbarDragLayer dragLayer) {
+                    return dragLayer.mBackgroundRenderer.getPaint().getAlpha() / 255f;
+                }
+            };
+
+
     private final TaskbarBackgroundRenderer mBackgroundRenderer;
     private final ViewTreeObserver.OnComputeInternalInsetsListener mTaskbarInsetsComputer =
             this::onComputeTaskbarInsets;
@@ -49,6 +71,8 @@
 
     private float mTaskbarBackgroundOffset;
 
+    private final MultiPropertyFactory<TaskbarDragLayer> mTaskbarBackgroundAlpha;
+
     public TaskbarDragLayer(@NonNull Context context) {
         this(context, null);
     }
@@ -66,7 +90,11 @@
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, 1 /* alphaChannelCount */);
         mBackgroundRenderer = new TaskbarBackgroundRenderer(mActivity);
-        mBackgroundRenderer.getPaint().setAlpha(0);
+
+        mTaskbarBackgroundAlpha = new MultiPropertyFactory<>(this, BG_ALPHA, INDEX_COUNT,
+                (a, b) -> a * b, 1f);
+        mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES).setValue(0);
+        mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM).setValue(1);
     }
 
     public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
@@ -133,13 +161,12 @@
         super.dispatchDraw(canvas);
     }
 
-    /**
-     * Sets the alpha of the background color behind all the Taskbar contents.
-     * @param alpha 0 is fully transparent, 1 is fully opaque.
-     */
-    protected void setTaskbarBackgroundAlpha(float alpha) {
-        mBackgroundRenderer.getPaint().setAlpha((int) (alpha * 255));
-        invalidate();
+    protected MultiProperty getBackgroundRendererAlpha() {
+        return mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES);
+    }
+
+    protected MultiProperty getBackgroundRendererAlphaForStash() {
+        return mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 888d535..867b062 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.util.DimensionUtils;
+import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.TouchController;
 
 import java.io.PrintWriter;
@@ -62,12 +63,14 @@
     private TaskbarStashViaTouchController mTaskbarStashViaTouchController;
     private AnimatedFloat mOnBackgroundNavButtonColorIntensity;
 
+    private MultiProperty mBackgroundRendererAlpha;
     private float mLastSetBackgroundAlpha;
 
     public TaskbarDragLayerController(TaskbarActivityContext activity,
             TaskbarDragLayer taskbarDragLayer) {
         mActivity = activity;
         mTaskbarDragLayer = taskbarDragLayer;
+        mBackgroundRendererAlpha = mTaskbarDragLayer.getBackgroundRendererAlpha();
         final Resources resources = mTaskbarDragLayer.getResources();
         mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
     }
@@ -152,11 +155,15 @@
                 * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value
                 * mAssistantBgTaskbar.value;
         mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
-        mTaskbarDragLayer.setTaskbarBackgroundAlpha(mLastSetBackgroundAlpha);
+        mBackgroundRendererAlpha.setValue(mLastSetBackgroundAlpha);
 
         updateOnBackgroundNavButtonColorIntensity();
     }
 
+    public MultiProperty getBackgroundRendererAlphaForStash() {
+        return mTaskbarDragLayer.getBackgroundRendererAlphaForStash();
+    }
+
     /**
      * Sets the translation of the background during the swipe up gesture.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 38d34fd..8f3898f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -22,8 +22,6 @@
 
 import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
 import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
-import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
-import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -51,6 +49,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -58,8 +57,6 @@
 import com.android.launcher3.taskbar.unfold.NonDestroyableScopedUnfoldTransitionProgressProvider;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.RecentsActivity;
@@ -79,6 +76,22 @@
     private static final String TAG = "TaskbarManager";
     private static final boolean DEBUG = false;
 
+    /**
+     * All the configurations which do not initiate taskbar recreation.
+     * This includes all the configurations defined in Launcher's manifest entry and
+     * ActivityController#filterConfigChanges
+     */
+    private static final int SKIP_RECREATE_CONFIG_CHANGES = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
+            | ActivityInfo.CONFIG_KEYBOARD
+            | ActivityInfo.CONFIG_KEYBOARD_HIDDEN
+            | ActivityInfo.CONFIG_MCC
+            | ActivityInfo.CONFIG_MNC
+            | ActivityInfo.CONFIG_NAVIGATION
+            | ActivityInfo.CONFIG_ORIENTATION
+            | ActivityInfo.CONFIG_SCREEN_SIZE
+            | ActivityInfo.CONFIG_SCREEN_LAYOUT
+            | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+
     public static final boolean FLAG_HIDE_NAVBAR_WINDOW =
             SystemProperties.getBoolean("persist.wm.debug.hide_navbar_window", false);
 
@@ -89,12 +102,11 @@
             Settings.Secure.NAV_BAR_KIDS_MODE);
 
     private final Context mContext;
-    private final DisplayController mDisplayController;
     private final TaskbarNavButtonController mNavButtonController;
-    private final SettingsCache.OnChangeListener mUserSetupCompleteListener;
-    private final SettingsCache.OnChangeListener mNavBarKidsModeListener;
     private final ComponentCallbacks mComponentCallbacks;
-    private final SimpleBroadcastReceiver mShutdownReceiver;
+
+    private final SimpleBroadcastReceiver mShutdownReceiver =
+            new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
 
     // The source for this provider is set when Launcher is available
     // We use 'non-destroyable' version here so the original provider won't be destroyed
@@ -102,7 +114,6 @@
     // It's destruction/creation will be managed by the activity.
     private final ScopedUnfoldTransitionProgressProvider mUnfoldProgressProvider =
             new NonDestroyableScopedUnfoldTransitionProgressProvider();
-    private NavigationMode mNavMode;
 
     private TaskbarActivityContext mTaskbarActivityContext;
     private StatefulActivity mActivity;
@@ -113,19 +124,11 @@
     private final TaskbarSharedState mSharedState = new TaskbarSharedState();
 
     /**
-     * We use WindowManager's ComponentCallbacks() for most of the config changes, however for
-     * navigation mode, that callback gets called too soon, before it's internal navigation mode
-     * reflects the current one.
-     * DisplayController's callback is delayed enough to get the correct nav mode value
-     *
-     * We also use density change here because DeviceProfile has had a chance to update it's state
-     * whereas density for component callbacks registered in this class don't update DeviceProfile.
-     * Confused? Me too. Make it less confusing (TODO: b/227669780)
-     *
-     * Flags used with {@link #mDispInfoChangeListener}
+     * We use WindowManager's ComponentCallbacks() for internal UI changes (similar to an Activity)
+     * which comes via a different channel
      */
-    private static final int CHANGE_FLAGS = CHANGE_NAVIGATION_MODE | CHANGE_DENSITY;
-    private final DisplayController.DisplayInfoChangeListener mDispInfoChangeListener;
+    private final OnIDPChangeListener mIdpChangeListener = c -> recreateTaskbar();
+    private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
 
     private boolean mUserUnlocked = false;
 
@@ -167,15 +170,11 @@
 
     @SuppressLint("WrongConstant")
     public TaskbarManager(TouchInteractionService service) {
-        mDisplayController = DisplayController.INSTANCE.get(service);
         Display display =
                 service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
         mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
         mNavButtonController = new TaskbarNavButtonController(service,
                 SystemUiProxy.INSTANCE.get(mContext), new Handler());
-        mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
-        mNavBarKidsModeListener = isNavBarKidsMode -> recreateTaskbar();
-        // TODO(b/227669780): Consolidate this w/ DisplayController callbacks
         mComponentCallbacks = new ComponentCallbacks() {
             private Configuration mOldConfig = mContext.getResources().getConfiguration();
 
@@ -186,80 +185,42 @@
                 DeviceProfile dp = mUserUnlocked
                         ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
                         : null;
-                int configDiff = mOldConfig.diff(newConfig);
-                int configDiffForRecreate = configDiff;
-                int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
-                        | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE
-                        | ActivityInfo.CONFIG_SCREEN_SIZE;
-                if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
-                        && mTaskbarActivityContext != null && dp != null
-                        && !isPhoneMode(dp)) {
-                    // Additional check since this callback gets fired multiple times w/o
-                    // screen size changing, or when simply rotating the device.
-                    // In the case of phone device rotation, we do want to call recreateTaskbar()
-                    DeviceProfile oldDp = mTaskbarActivityContext.getDeviceProfile();
-                    boolean isOrientationChange =
-                            (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0;
+                int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
 
-                    int newOrientation = newConfig.windowConfiguration.getRotation();
-                    int oldOrientation = mOldConfig.windowConfiguration.getRotation();
-                    int oldWidth = isOrientationChange ? oldDp.heightPx : oldDp.widthPx;
-                    int oldHeight = isOrientationChange ? oldDp.widthPx : oldDp.heightPx;
-
-                    if ((dp.widthPx == oldWidth && dp.heightPx == oldHeight)
-                            || (newOrientation == oldOrientation)) {
-                        configDiffForRecreate &= ~ActivityInfo.CONFIG_SCREEN_SIZE;
-                    }
-                }
                 if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
                     // Only recreate for theme changes, not other UI mode changes such as docking.
                     int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
                     int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
                     if (oldUiNightMode == newUiNightMode) {
-                        configDiffForRecreate &= ~ActivityInfo.CONFIG_UI_MODE;
+                        configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
                     }
                 }
 
                 debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() "
-                        + "configDiffForRecreate="
-                        + Configuration.configurationDiffToString(configDiffForRecreate));
-                if ((configDiffForRecreate & configsRequiringRecreate) != 0) {
+                        + "configDiff=" + Configuration.configurationDiffToString(configDiff));
+                if (configDiff != 0 || mTaskbarActivityContext == null) {
                     recreateTaskbar();
                 } else {
                     // Config change might be handled without re-creating the taskbar
-                    if (mTaskbarActivityContext != null) {
-                        if (dp != null && !isTaskbarPresent(dp)) {
-                            destroyExistingTaskbar();
-                        } else {
-                            if (dp != null && isTaskbarPresent(dp)) {
-                                mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
-                            }
-                            mTaskbarActivityContext.onConfigurationChanged(configDiff);
+                    if (dp != null && !isTaskbarPresent(dp)) {
+                        destroyExistingTaskbar();
+                    } else {
+                        if (dp != null && isTaskbarPresent(dp)) {
+                            mTaskbarActivityContext.updateDeviceProfile(dp);
                         }
+                        mTaskbarActivityContext.onConfigurationChanged(configDiff);
                     }
                 }
-                mOldConfig = newConfig;
+                mOldConfig = new Configuration(newConfig);
             }
 
             @Override
             public void onLowMemory() { }
         };
-        mShutdownReceiver = new SimpleBroadcastReceiver(i ->
-                destroyExistingTaskbar());
-        mDispInfoChangeListener = (context, info, flags) -> {
-            if ((flags & CHANGE_FLAGS) != 0) {
-                mNavMode = info.navigationMode;
-                recreateTaskbar();
-            }
-            debugWhyTaskbarNotDestroyed("DisplayInfoChangeListener#"
-                    + mDisplayController.getChangeFlagsString(flags));
-        };
-        mNavMode = mDisplayController.getInfo().navigationMode;
-        mDisplayController.addChangeListener(mDispInfoChangeListener);
-        SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
-                mUserSetupCompleteListener);
-        SettingsCache.INSTANCE.get(mContext).register(NAV_BAR_KIDS_MODE,
-                mNavBarKidsModeListener);
+        SettingsCache.INSTANCE.get(mContext)
+                .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
+        SettingsCache.INSTANCE.get(mContext)
+                .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
         mContext.registerComponentCallbacks(mComponentCallbacks);
         mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
         UI_HELPER_EXECUTOR.execute(() -> {
@@ -315,6 +276,7 @@
      */
     public void onUserUnlocked() {
         mUserUnlocked = true;
+        LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
         recreateTaskbar();
     }
 
@@ -398,7 +360,7 @@
             mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
                     mUnfoldProgressProvider);
         } else {
-            mTaskbarActivityContext.updateDeviceProfile(dp, mNavMode);
+            mTaskbarActivityContext.updateDeviceProfile(dp);
         }
         mTaskbarActivityContext.init(mSharedState);
 
@@ -501,11 +463,13 @@
         UI_HELPER_EXECUTOR.execute(
                 () -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
         destroyExistingTaskbar();
-        mDisplayController.removeChangeListener(mDispInfoChangeListener);
-        SettingsCache.INSTANCE.get(mContext).unregister(USER_SETUP_COMPLETE_URI,
-                mUserSetupCompleteListener);
-        SettingsCache.INSTANCE.get(mContext).unregister(NAV_BAR_KIDS_MODE,
-                mNavBarKidsModeListener);
+        if (mUserUnlocked) {
+            LauncherAppState.getIDP(mContext).removeOnChangeListener(mIdpChangeListener);
+        }
+        SettingsCache.INSTANCE.get(mContext)
+                .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
+        SettingsCache.INSTANCE.get(mContext)
+                .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
         mContext.unregisterComponentCallbacks(mComponentCallbacks);
         mContext.unregisterReceiver(mShutdownReceiver);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 3f2396b..fab70d3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -232,6 +232,7 @@
     // Taskbar background properties.
     private AnimatedFloat mTaskbarBackgroundOffset;
     private AnimatedFloat mTaskbarImeBgAlpha;
+    private MultiProperty mTaskbarBackgroundAlphaForStash;
     // TaskbarView icon properties.
     private MultiProperty mIconAlphaForStash;
     private AnimatedFloat mIconScaleForStash;
@@ -304,6 +305,7 @@
         TaskbarDragLayerController dragLayerController = controllers.taskbarDragLayerController;
         mTaskbarBackgroundOffset = dragLayerController.getTaskbarBackgroundOffset();
         mTaskbarImeBgAlpha = dragLayerController.getImeBgTaskbar();
+        mTaskbarBackgroundAlphaForStash = dragLayerController.getBackgroundRendererAlphaForStash();
 
         TaskbarViewController taskbarViewController = controllers.taskbarViewController;
         mIconAlphaForStash = taskbarViewController.getTaskbarIconAlpha().get(
@@ -786,37 +788,42 @@
         final float backgroundOffsetTarget = isStashed ? 1 : 0;
         final float iconAlphaTarget = isStashed ? 0 : 1;
         final float stashedHandleAlphaTarget = isStashed ? 1 : 0;
+        final float backgroundAlphaTarget = isStashed ? 0 : 1;
 
         // Timing for the alpha values depend on the animation played
-        long iconAlphaStartDelay = 0, iconAlphaDuration = 0, stashedHandleAlphaDelay = 0,
-                stashedHandleAlphaDuration = 0;
+        long iconAlphaStartDelay = 0, iconAlphaDuration = 0, backgroundAndHandleAlphaStartDelay = 0,
+                backgroundAndHandleAlphaDuration = 0;
         if (duration > 0) {
             if (animationType == TRANSITION_HANDLE_FADE) {
                 // When fading, the handle fades in/out at the beginning of the transition with
                 // TASKBAR_STASH_ALPHA_DURATION.
-                stashedHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+                backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
                 // The iconAlphaDuration must be set to duration for the skippable interpolators
                 // below to work.
                 iconAlphaDuration = duration;
             } else {
                 iconAlphaStartDelay = TASKBAR_STASH_ALPHA_START_DELAY;
                 iconAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
-                stashedHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
+                backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
 
                 if (isStashed) {
                     if (animationType == TRANSITION_HOME_TO_APP) {
                         iconAlphaStartDelay = TASKBAR_STASH_ICON_ALPHA_HOME_TO_APP_START_DELAY;
                     }
-                    stashedHandleAlphaDelay = iconAlphaStartDelay;
-                    stashedHandleAlphaDuration = Math.max(0, duration - iconAlphaStartDelay);
+                    backgroundAndHandleAlphaStartDelay = iconAlphaStartDelay;
+                    backgroundAndHandleAlphaDuration = Math.max(0, duration - iconAlphaStartDelay);
                 }
 
             }
         }
 
         play(as, mTaskbarStashedHandleAlpha.animateToValue(stashedHandleAlphaTarget),
-                stashedHandleAlphaDelay,
-                stashedHandleAlphaDuration, LINEAR);
+                backgroundAndHandleAlphaStartDelay,
+                backgroundAndHandleAlphaDuration, LINEAR);
+
+        play(as, mTaskbarBackgroundAlphaForStash.animateToValue(backgroundAlphaTarget),
+                backgroundAndHandleAlphaStartDelay,
+                backgroundAndHandleAlphaDuration, LINEAR);
 
         // The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
         AnimatorSet skippable = as;
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
index 942a0a1..582dcc7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
@@ -34,4 +34,4 @@
 ) : BubbleBarItem(info.key, view)
 
 /** Represents the overflow bubble in the bubble bar. */
-data class BubbleBarOverflow(override val view: BubbleView) : BubbleBarItem("overflow", view)
+data class BubbleBarOverflow(override val view: BubbleView) : BubbleBarItem("Overflow", view)
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 0e1e0e1..8d20705 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -150,7 +150,9 @@
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
         if (getChildCount() + 1 > MAX_BUBBLES) {
-            removeViewInLayout(getChildAt(getChildCount() - 1));
+            // the last child view is the overflow bubble and we shouldn't remove that. remove the
+            // second to last child view.
+            removeViewInLayout(getChildAt(getChildCount() - 2));
         }
         super.addView(child, index, params);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 8c8e267..d241260 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -113,7 +114,9 @@
         setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
-        if (mRecentsView.isSplitSelectionActive()) {
+        boolean exitingOverview = !FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()
+                || !toState.overviewUi;
+        if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
             // TODO (b/238651489): Refactor state management to avoid need for double check
             FloatingTaskView floatingTask = mRecentsView.getFirstFloatingTaskView();
             if (floatingTask != null) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index ecc8e19..512d5f4 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -615,9 +615,10 @@
         mSplitSelectStateController.findLastActiveTaskAndRunCallback(
                 splitSelectSource.itemInfo.getComponentKey(),
                 foundTask -> {
-                    splitSelectSource.alreadyRunningTaskId = foundTask == null
-                            ? INVALID_TASK_ID
-                            : foundTask.key.id;
+                    boolean taskWasFound = foundTask != null;
+                    splitSelectSource.alreadyRunningTaskId = taskWasFound
+                            ? foundTask.key.id
+                            : INVALID_TASK_ID;
                     if (ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
                         startSplitToHome(splitSelectSource);
                     } else {
@@ -1295,7 +1296,7 @@
                 groupTask.task1.key.id,
                 groupTask.task2.key.id,
                 SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
-                /* callback= */ success -> {},
+                /* callback= */ success -> mSplitSelectStateController.resetState(),
                 /* freezeTaskList= */ true,
                 groupTask.mSplitBounds == null
                         ? DEFAULT_SPLIT_RATIO
@@ -1304,6 +1305,13 @@
                                 : groupTask.mSplitBounds.leftTaskPercent);
     }
 
+    @Override
+    public boolean isCommandQueueEmpty() {
+        OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
+        return super.isCommandQueueEmpty()
+                && (overviewCommandHelper == null || overviewCommandHelper.isCommandQueueEmpty());
+    }
+
     private static final class LauncherTaskViewController extends
             TaskViewTouchController<Launcher> {
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 9f9cb00..d3ef589 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
 
 import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
 import static com.android.app.animation.Interpolators.DECELERATE_3;
@@ -261,7 +262,7 @@
         xAnim.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1], LINEAR);
         // Use QuickSwitchState instead of OverviewState to determine scrim color,
         // since we need to take potential taskbar into account.
-        xAnim.setScrimViewBackgroundColor(mLauncher.getScrimView(),
+        xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
                 QUICK_SWITCH_FROM_HOME.getWorkspaceScrimColor(mLauncher), LINEAR);
         if (mRecentsView.getTaskViewCount() == 0) {
             xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 6ab3bbe..95672f3 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1530,6 +1530,21 @@
                 if (mSwipePipToHomeReleaseCheck != null) {
                     mSwipePipToHomeReleaseCheck.setCanRelease(false);
                 }
+
+                // grab a screenshot before the PipContentOverlay gets parented on top of the task
+                UI_HELPER_EXECUTOR.execute(() -> {
+                    final int taskId = mGestureState.getRunningTaskId();
+                    mTaskSnapshotCache.put(taskId,
+                            mRecentsAnimationController.screenshotTask(taskId));
+                });
+
+                // let SystemUi reparent the overlay leash as soon as possible
+                SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
+                        mSwipePipToHomeAnimator.getTaskId(),
+                        mSwipePipToHomeAnimator.getComponentName(),
+                        mSwipePipToHomeAnimator.getDestinationBounds(),
+                        mSwipePipToHomeAnimator.getContentOverlay());
+
                 windowAnim = mSwipePipToHomeAnimators;
             } else {
                 mSwipePipToHomeAnimator = null;
@@ -2108,11 +2123,6 @@
      */
     private void maybeFinishSwipePipToHome() {
         if (mIsSwipingPipToHome && mSwipePipToHomeAnimators[0] != null) {
-            SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
-                    mSwipePipToHomeAnimator.getTaskId(),
-                    mSwipePipToHomeAnimator.getComponentName(),
-                    mSwipePipToHomeAnimator.getDestinationBounds(),
-                    mSwipePipToHomeAnimator.getContentOverlay());
             mRecentsAnimationController.setFinishTaskTransaction(
                     mSwipePipToHomeAnimator.getTaskId(),
                     mSwipePipToHomeAnimator.getFinishTransaction(),
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index a0d49a4..4a60566 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -140,6 +140,11 @@
         mPendingCommands.clear();
     }
 
+    @UiThread
+    public boolean isCommandQueueEmpty() {
+        return mPendingCommands.isEmpty();
+    }
+
     @Nullable
     private TaskView getNextTask(RecentsView view) {
         final TaskView runningTaskView = view.getRunningTaskView();
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index bf96690..e282d1f 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -466,4 +466,11 @@
             }
         };
     }
+
+    @Override
+    public boolean isCommandQueueEmpty() {
+        OverviewCommandHelper overviewCommandHelper = mTISBindHelper.getOverviewCommandHelper();
+        return super.isCommandQueueEmpty()
+                && (overviewCommandHelper == null || overviewCommandHelper.isCommandQueueEmpty());
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 27fb476..84246e9 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -19,6 +19,7 @@
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Rect;
+import android.util.Log;
 import android.view.RemoteAnimationTarget;
 
 import androidx.annotation.Nullable;
@@ -37,6 +38,8 @@
  * {@link TaskViewSimulator}
  */
 public class RemoteTargetGluer {
+    private static final String TAG = "RemoteTargetGluer";
+
     private static final int DEFAULT_NUM_HANDLES = 2;
 
     private RemoteTargetHandle[] mRemoteTargetHandles;
@@ -118,7 +121,9 @@
         long appCount = Arrays.stream(targets.apps)
                 .filter(app -> app.mode == targets.targetMode)
                 .count();
+        Log.d(TAG, "appCount: " + appCount + " handleLength: " + mRemoteTargetHandles.length);
         if (appCount < mRemoteTargetHandles.length) {
+            Log.d(TAG, "resizing handles");
             RemoteTargetHandle[] newHandles = new RemoteTargetHandle[(int) appCount];
             System.arraycopy(mRemoteTargetHandles, 0/*src*/, newHandles, 0/*dst*/, (int) appCount);
             mRemoteTargetHandles = newHandles;
@@ -128,6 +133,8 @@
                 .anyMatch(remoteAnimationTarget ->
                         remoteAnimationTarget.windowConfiguration.getWindowingMode()
                                 == WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+        Log.d(TAG, "containsSplitTargets? " + containsSplitTargets + " handleLength: " +
+                mRemoteTargetHandles.length + " appsLength: " + targets.apps.length);
 
         if (mRemoteTargetHandles.length == 1) {
             // Single fullscreen app
diff --git a/quickstep/src/com/android/quickstep/SplitSelectionListener.kt b/quickstep/src/com/android/quickstep/SplitSelectionListener.kt
new file mode 100644
index 0000000..5025c1c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SplitSelectionListener.kt
@@ -0,0 +1,17 @@
+package com.android.quickstep
+
+interface SplitSelectionListener {
+    /** Called when the first app has been selected with the intention to launch split screen */
+    fun onSplitSelectionActive()
+
+    /** Called when the second app has been selected with the intention to launch split screen */
+    fun onSplitSelectionConfirmed()
+
+    /**
+     * Called when the user no longer is in the process of selecting apps for split screen.
+     * [launchedSplit] will be true if selected apps have launched successfully (either in
+     * split screen or fullscreen), false if the user canceled/exited the selection process
+     */
+    fun onSplitSelectionExit(launchedSplit: Boolean) {
+    }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index d40558c..dd6499b 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -563,9 +563,9 @@
     }
 
     /**
-     * Notifies WM Shell that launcher has finished all the animation for swipe to home. WM Shell
-     * can choose to fade out the overlay when entering PIP is finished, and WM Shell should be
-     * responsible for cleaning up the overlay.
+     * Notifies WM Shell that launcher has finished the preparation of the animation for swipe to
+     * home. WM Shell can choose to fade out the overlay when entering PIP is finished, and WM Shell
+     * should be responsible for cleaning up the overlay.
      */
     public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
             SurfaceControl overlay) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index c8af252..5998c35 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -83,7 +83,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.provider.RestoreDbTask;
@@ -143,8 +142,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
-import java.util.Arrays;
-import java.util.LinkedList;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -1274,80 +1271,41 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
-        if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
-            LinkedList<String> args = new LinkedList(Arrays.asList(rawArgs));
-            switch (args.pollFirst()) {
-                case "cmd":
-                    if (args.peekFirst() == null) {
-                        printAvailableCommands(pw);
-                    } else {
-                        onCommand(pw, args);
-                    }
-                    break;
-            }
-        } else {
-            // Dump everything
-            FlagsFactory.dump(pw);
-            if (LockedUserState.get(this).isUserUnlocked()) {
-                PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
-            }
-            mDeviceState.dump(pw);
-            if (mOverviewComponentObserver != null) {
-                mOverviewComponentObserver.dump(pw);
-            }
-            if (mOverviewCommandHelper != null) {
-                mOverviewCommandHelper.dump(pw);
-            }
-            if (mGestureState != null) {
-                mGestureState.dump(pw);
-            }
-            pw.println("Input state:");
-            pw.println("  mInputMonitorCompat=" + mInputMonitorCompat);
-            pw.println("  mInputEventReceiver=" + mInputEventReceiver);
-            DisplayController.INSTANCE.get(this).dump(pw);
-            pw.println("TouchState:");
-            BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
-                    : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
-            boolean resumed = mOverviewComponentObserver != null
-                    && mOverviewComponentObserver.getActivityInterface().isResumed();
-            pw.println("  createdOverviewActivity=" + createdOverviewActivity);
-            pw.println("  resumed=" + resumed);
-            pw.println("  mConsumer=" + mConsumer.getName());
-            ActiveGestureLog.INSTANCE.dump("", pw);
-            RecentsModel.INSTANCE.get(this).dump("", pw);
-            pw.println("ProtoTrace:");
-            pw.println("  file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
-            if (createdOverviewActivity != null) {
-                createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
-            }
-            mTaskbarManager.dumpLogs("", pw);
+        // Dump everything
+        FlagsFactory.dump(pw);
+        if (LockedUserState.get(this).isUserUnlocked()) {
+            PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
         }
-    }
-
-    private void printAvailableCommands(PrintWriter pw) {
-        pw.println("Available commands:");
-        pw.println("  clear-touch-log: Clears the touch interaction log");
-        pw.println("  print-gesture-log: only prints the ActiveGestureLog dump");
-    }
-
-    private void onCommand(PrintWriter pw, LinkedList<String> args) {
-        String cmd = args.pollFirst();
-        if (cmd == null) {
-            pw.println("Command missing");
-            printAvailableCommands(pw);
-            return;
+        mDeviceState.dump(pw);
+        if (mOverviewComponentObserver != null) {
+            mOverviewComponentObserver.dump(pw);
         }
-        switch (cmd) {
-            case "clear-touch-log":
-                ActiveGestureLog.INSTANCE.clear();
-                break;
-            case "print-gesture-log":
-                ActiveGestureLog.INSTANCE.dump("", pw);
-                break;
-            default:
-                pw.println("Command does not exist: " + cmd);
-                printAvailableCommands(pw);
+        if (mOverviewCommandHelper != null) {
+            mOverviewCommandHelper.dump(pw);
         }
+        if (mGestureState != null) {
+            mGestureState.dump(pw);
+        }
+        pw.println("Input state:");
+        pw.println("  mInputMonitorCompat=" + mInputMonitorCompat);
+        pw.println("  mInputEventReceiver=" + mInputEventReceiver);
+        DisplayController.INSTANCE.get(this).dump(pw);
+        pw.println("TouchState:");
+        BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
+                : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+        boolean resumed = mOverviewComponentObserver != null
+                && mOverviewComponentObserver.getActivityInterface().isResumed();
+        pw.println("  createdOverviewActivity=" + createdOverviewActivity);
+        pw.println("  resumed=" + resumed);
+        pw.println("  mConsumer=" + mConsumer.getName());
+        ActiveGestureLog.INSTANCE.dump("", pw);
+        RecentsModel.INSTANCE.get(this).dump("", pw);
+        pw.println("ProtoTrace:");
+        pw.println("  file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
+        if (createdOverviewActivity != null) {
+            createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
+        }
+        mTaskbarManager.dumpLogs("", pw);
     }
 
     private AbsSwipeUpHandler createLauncherSwipeHandler(
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index ca8381b..8a9e04e 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -114,7 +114,7 @@
         setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
                 state.showTaskThumbnailSplash() ? 1f : 0f, INSTANT);
 
-        setter.setScrimViewBackgroundColor(mActivity.getScrimView(), state.getScrimColor(mActivity),
+        setter.setViewBackgroundColor(mActivity.getScrimView(), state.getScrimColor(mActivity),
                 config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
 
         RecentsState currentState = mActivity.getStateManager().getState();
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 074aedd..a9a57c6 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -35,6 +35,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.SplitConfigurationOptions;
@@ -79,11 +80,16 @@
     }
 
     @Override
-    public void startHome(boolean animated) {
+    protected void handleStartHome(boolean animated) {
         mActivity.startHome();
         AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
     }
 
+    @Override
+    public boolean isCommandQueueEmpty() {
+        return mActivity.isCommandQueueEmpty();
+    }
+
     /**
      * When starting gesture interaction from home, we add a temporary invisible tile corresponding
      * to the home task. This allows us to handle quick-switch similarly to a quick-switching
@@ -246,7 +252,11 @@
             setOverviewSelectEnabled(false);
         }
         if (finalState != OVERVIEW_SPLIT_SELECT) {
-            resetFromSplitSelectionState();
+            if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                mSplitSelectStateController.resetState();
+            } else {
+                resetFromSplitSelectionState();
+            }
         }
 
         if (isOverlayEnabled) {
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index 409bf9c..cca4d52 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -18,12 +18,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.config.FeatureFlags;
-
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
@@ -150,10 +147,6 @@
         lastEventEntries.add(eventEntry);
     }
 
-    public void clear() {
-        Arrays.fill(logs, null);
-    }
-
     public void dump(String prefix, PrintWriter writer) {
         writer.println(prefix + "ActiveGestureErrorDetector:");
         for (int i = 0; i < logs.length; i++) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 2123253..e063b44 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -73,16 +73,18 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SplitSelectionListener;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.views.FloatingTaskView;
 import com.android.quickstep.views.GroupedTaskView;
-import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -138,6 +140,8 @@
 
     private FloatingTaskView mFirstFloatingTaskView;
 
+    private final List<SplitSelectionListener> mSplitSelectionListeners = new ArrayList<>();
+
     public SplitSelectStateController(Context context, Handler handler, StateManager stateManager,
             DepthController depthController, StatsLogManager statsLogManager,
             SystemUiProxy systemUiProxy, RecentsModel recentsModel) {
@@ -248,6 +252,27 @@
     }
 
     /**
+     * Listener will only get callbacks going forward from the point of registration. No
+     * methods will be fired upon registering.
+     */
+    public void registerSplitListener(@NonNull SplitSelectionListener listener) {
+        if (mSplitSelectionListeners.contains(listener)) {
+            return;
+        }
+        mSplitSelectionListeners.add(listener);
+    }
+
+    public void unregisterSplitListener(@NonNull SplitSelectionListener listener) {
+        mSplitSelectionListeners.remove(listener);
+    }
+
+    private void dispatchOnSplitSelectionExit() {
+        for (SplitSelectionListener listener : mSplitSelectionListeners) {
+            listener.onSplitSelectionExit(false);
+        }
+    }
+
+    /**
      * To be called when the actual tasks ({@link #mInitialTaskId}, {@link #mSecondTaskId}) are
      * to be launched. Call after launcher side animations are complete.
      */
@@ -790,12 +815,16 @@
     }
 
     /**
-     * To be called if split select was cancelled
+     * To be called whenever we exit split selection state. If
+     * {@link FeatureFlags#ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE} is set, this should be the
+     * central way split is getting reset, which should then go through the callbacks to reset
+     * other state.
      */
     public void resetState() {
         if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
             mSplitSelectDataHolder.resetState();
         }
+        dispatchOnSplitSelectionExit();
         mInitialTaskId = INVALID_TASK_ID;
         mInitialTaskIntent = null;
         mSecondTaskId = INVALID_TASK_ID;
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 3e79eaf..7bbe36a 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -280,10 +280,6 @@
         }
     }
 
-    public String getContentDescription() {
-            return getContentDescriptionForTask(mTask, mAppUsageLimitTimeMs, mAppRemainingTimeMs);
-    }
-
     private String getContentDescriptionForTask(
             Task task, long appUsageLimitTimeMs, long appRemainingTimeMs) {
         return appUsageLimitTimeMs >= 0 && appRemainingTimeMs >= 0 ?
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 4dbf4e3..9e9b22f 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -37,6 +37,7 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -81,7 +82,7 @@
     }
 
     @Override
-    public void startHome(boolean animated) {
+    protected void handleStartHome(boolean animated) {
         StateManager stateManager = mActivity.getStateManager();
         animated &= stateManager.shouldAnimateStateChange();
         stateManager.goToState(NORMAL, animated);
@@ -89,6 +90,11 @@
     }
 
     @Override
+    public boolean isCommandQueueEmpty() {
+        return mActivity.isCommandQueueEmpty();
+    }
+
+    @Override
     protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
             mActivity.getStateManager().moveToRestState();
@@ -218,7 +224,11 @@
 
     @Override
     protected boolean canLaunchFullscreenTask() {
-        return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            return !mSplitSelectStateController.isSplitSelectActive();
+        } else {
+            return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 4d88a04..f0daf8d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -173,6 +173,7 @@
 import com.android.quickstep.RemoteTargetGluer;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
 import com.android.quickstep.RotationTouchHelper;
+import com.android.quickstep.SplitSelectionListener;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
@@ -662,7 +663,8 @@
     /**
      * Placeholder view indicating where the first split screen selected app will be placed
      */
-    private SplitSelectStateController mSplitSelectStateController;
+    protected SplitSelectStateController mSplitSelectStateController;
+
     /**
      * The first task that split screen selection was initiated with. When split select state is
      * initialized, we create a
@@ -685,6 +687,19 @@
     @Nullable
     private SplitSelectSource mSplitSelectSource;
 
+    private final SplitSelectionListener mSplitSelectionListener = new SplitSelectionListener() {
+        @Override
+        public void onSplitSelectionConfirmed() { }
+
+        @Override
+        public void onSplitSelectionActive() { }
+
+        @Override
+        public void onSplitSelectionExit(boolean launchedSplit) {
+            resetFromSplitSelectionState();
+        }
+    };
+
     /**
      * Keeps track of the index of the TaskView that split screen was initialized with so we know
      * where to insert it back into list of taskViews in case user backs out of entering split
@@ -1065,6 +1080,9 @@
                 mIPipAnimationListener);
         mOrientationState.initListeners();
         mTaskOverlayFactory.initListeners();
+        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
+        }
     }
 
     @Override
@@ -1083,6 +1101,9 @@
         mIPipAnimationListener.setActivityAndRecentsView(null, null);
         mOrientationState.destroyListeners();
         mTaskOverlayFactory.removeListeners();
+        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
+        }
     }
 
     @Override
@@ -1660,8 +1681,8 @@
         // If we are entering Overview as a result of initiating a split from somewhere else
         // (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail.
         int stagedTaskIdToBeRemovedFromGrid;
-        if (mSplitSelectSource != null) {
-            stagedTaskIdToBeRemovedFromGrid = mSplitSelectSource.alreadyRunningTaskId;
+        if (isSplitSelectionActive()) {
+            stagedTaskIdToBeRemovedFromGrid = mSplitSelectStateController.getInitialTaskId();
             updateCurrentTaskActionsVisibility();
         } else {
             stagedTaskIdToBeRemovedFromGrid = INVALID_TASK_ID;
@@ -2345,7 +2366,15 @@
         startHome(mActivity.isStarted());
     }
 
-    public abstract void startHome(boolean animated);
+    public void startHome(boolean animated) {
+        if (!isCommandQueueEmpty()) return;
+        handleStartHome(animated);
+    }
+
+    protected abstract void handleStartHome(boolean animated);
+
+    /** Returns whether the overview command helper queue is empty. */
+    public abstract boolean isCommandQueueEmpty();
 
     public void reset() {
         setCurrentTask(-1);
@@ -2367,7 +2396,9 @@
             remoteTargetHandle.getTransformParams().setTargetSet(null);
             remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
         });
-        resetFromSplitSelectionState();
+        if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            resetFromSplitSelectionState();
+        }
 
         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
         // visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -3251,7 +3282,11 @@
                         InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
             } else {
                 // If transition to split select was interrupted, clean up to prevent glitches
-                resetFromSplitSelectionState();
+                if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                    mSplitSelectStateController.resetState();
+                } else {
+                    resetFromSplitSelectionState();
+                }
                 InteractionJankMonitorWrapper.cancel(
                         InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
             }
@@ -3284,8 +3319,13 @@
                 true /* isStagedTask */);
 
         pendingAnimation.addEndListener(animationSuccess ->
-                mSplitSelectStateController.launchInitialAppFullscreen(launchSuccess ->
-                        resetFromSplitSelectionState()));
+                mSplitSelectStateController.launchInitialAppFullscreen(launchSuccess -> {
+                    if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                        mSplitSelectStateController.resetState();
+                    } else {
+                        resetFromSplitSelectionState();
+                    }
+                }));
 
         pendingAnimation.buildAnim().start();
     }
@@ -3766,19 +3806,33 @@
                                         taskViewIdArray.removeValue(
                                                 finalNextFocusedTaskView.getTaskViewId());
                                     }
-                                    if (snappedIndex < taskViewIdArray.size()) {
-                                        taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
-                                    } else if (snappedIndex == taskViewIdArray.size()) {
-                                        // If the snapped task is the last item from the
-                                        // dismissed row,
-                                        // snap to the same column in the other grid row
-                                        IntArray inverseRowTaskViewIdArray =
-                                                isSnappedTaskInTopRow ? getBottomRowIdArray()
-                                                        : getTopRowIdArray();
-                                        if (snappedIndex < inverseRowTaskViewIdArray.size()) {
-                                            taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
-                                                    snappedIndex);
+                                    try {
+                                        if (snappedIndex < taskViewIdArray.size()) {
+                                            taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex);
+                                        } else if (snappedIndex == taskViewIdArray.size()) {
+                                            // If the snapped task is the last item from the
+                                            // dismissed row,
+                                            // snap to the same column in the other grid row
+                                            IntArray inverseRowTaskViewIdArray =
+                                                    isSnappedTaskInTopRow ? getBottomRowIdArray()
+                                                            : getTopRowIdArray();
+                                            if (snappedIndex < inverseRowTaskViewIdArray.size()) {
+                                                taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
+                                                        snappedIndex);
+                                            }
                                         }
+                                    } catch (ArrayIndexOutOfBoundsException e) {
+                                        throw new IllegalStateException(
+                                                "b/269956477 invalid snappedIndex"
+                                                        + "\nsnappedTaskViewId: "
+                                                        + snappedTaskViewId
+                                                        + "\nfocusedTaskViewId: "
+                                                        + mFocusedTaskViewId
+                                                        + "\ntopRowIdArray: "
+                                                        + getTopRowIdArray().toConcatString()
+                                                        + "\nbottomRowIdArray: "
+                                                        + getBottomRowIdArray().toConcatString(),
+                                                e);
                                     }
                                 }
                             }
@@ -4723,7 +4777,13 @@
 
         pendingAnimation.addEndListener(aBoolean -> {
             mSplitSelectStateController.launchSplitTasks(
-                    aBoolean1 -> RecentsView.this.resetFromSplitSelectionState());
+                    aBoolean1 -> {
+                        if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+                            mSplitSelectStateController.resetState();
+                        } else {
+                            resetFromSplitSelectionState();
+                        }
+                    });
             InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
         });
 
@@ -4745,7 +4805,8 @@
 
     @SuppressLint("WrongCall")
     protected void resetFromSplitSelectionState() {
-        if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1) {
+        if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1 ||
+                FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
             safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
             safeRemoveDragLayerView(mSecondFloatingTaskView);
             safeRemoveDragLayerView(mSplitInstructionsView);
@@ -4764,7 +4825,11 @@
         setTaskViewsPrimarySplitTranslation(0);
         setTaskViewsSecondarySplitTranslation(0);
 
-        mSplitSelectStateController.resetState();
+        if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
+            // When flag is on, this method gets called from resetState() call below, let's avoid
+            // infinite recursion today
+            mSplitSelectStateController.resetState();
+        }
         if (mSplitHiddenTaskViewIndex == -1) {
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index ea45f9d..40e3dca 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -1021,7 +1021,10 @@
             }
             if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
                 mIconLoadRequest = iconCache.updateIconInBackground(mTask,
-                        (task) -> setIcon(mIconView, task.icon));
+                        (task) -> {
+                            setIcon(mIconView, task.icon);
+                            mDigitalWellBeingToast.initialize(task);
+                        });
             }
         } else {
             if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
@@ -1343,7 +1346,6 @@
 
     protected void refreshTaskThumbnailSplash() {
         mSnapshotView.refreshSplashView();
-        setContentDescription(mDigitalWellBeingToast.getContentDescription());
     }
 
     private void setSplitSelectTranslationX(float x) {
diff --git a/res/color-night-v31/taskbar_background.xml b/res/color-night-v31/taskbar_background.xml
index 074db20..ec7a682 100644
--- a/res/color-night-v31/taskbar_background.xml
+++ b/res/color-night-v31/taskbar_background.xml
@@ -13,6 +13,8 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!-- Make sure to align any changes to
+     frameworks/base/libs/WindowManager/Shell/res/color/taskbar_background_dark.xml -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:color="@android:color/system_neutral1_500" android:lStar="6" />
 </selector>
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 9868e20..55dd1de 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -19,7 +19,6 @@
     android:layout_height="wrap_content"
     android:paddingHorizontal="@dimen/widget_cell_horizontal_padding"
     android:paddingVertical="@dimen/widget_cell_vertical_padding"
-    android:layout_marginHorizontal="@dimen/widget_cell_horizontal_padding"
     android:layout_weight="1"
     android:orientation="vertical"
     android:focusable="true"
diff --git a/res/layout/widgets_table_container.xml b/res/layout/widgets_table_container.xml
index 4a32672..c41d0bb 100644
--- a/res/layout/widgets_table_container.xml
+++ b/res/layout/widgets_table_container.xml
@@ -16,6 +16,7 @@
 <com.android.launcher3.widget.picker.WidgetsListTableView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/widgets_table"
+    android:paddingHorizontal="@dimen/widget_list_horizontal_margin"
     android:background="@drawable/bg_widgets_content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content" />
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 1aab8de..76a1239 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -204,6 +204,13 @@
         <!-- File that contains the specs for the workspace.
         Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
         <attr name="workspaceSpecsId" format="reference" />
+        <!-- File that contains the specs for all apps.
+        Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
+        <attr name="allAppsSpecsId" format="reference" />
+
+        <!-- File that contains the specs for the workspace.
+        Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
+        <attr name="folderSpecsId" format="reference" />
 
         <!-- By default all categories are enabled -->
         <attr name="deviceCategory" format="integer">
diff --git a/res/values/config.xml b/res/values/config.xml
index 27211fd..8f9731c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -217,4 +217,28 @@
     <!-- Whether the floating rotation button should be on the left/right in the device's natural
          orientation -->
     <bool name="floating_rotation_button_position_left">true</bool>
+
+    <!--  Mapping of visual icon size to XML value http://b/235886078  -->
+    <dimen name="iconSize48dp">52dp</dimen>
+    <dimen name="iconSize50dp">55dp</dimen>
+    <dimen name="iconSize52dp">57dp</dimen>
+    <dimen name="iconSize54dp">59dp</dimen>
+    <dimen name="iconSize56dp">61dp</dimen>
+    <dimen name="iconSize58dp">63dp</dimen>
+    <dimen name="iconSize60dp">66dp</dimen>
+    <dimen name="iconSize66dp">72dp</dimen>
+    <dimen name="iconSize72dp">79dp</dimen>
+
+    <!--  Icon size steps in dp  -->
+    <integer-array name="icon_size_steps">
+        <item>@dimen/iconSize48dp</item>
+        <item>@dimen/iconSize50dp</item>
+        <item>@dimen/iconSize52dp</item>
+        <item>@dimen/iconSize54dp</item>
+        <item>@dimen/iconSize56dp</item>
+        <item>@dimen/iconSize58dp</item>
+        <item>@dimen/iconSize60dp</item>
+        <item>@dimen/iconSize66dp</item>
+        <item>@dimen/iconSize72dp</item>
+    </integer-array>
 </resources>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 32421a4..64ac841 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -382,8 +382,7 @@
     private void resetCellSizeInternal(DeviceProfile deviceProfile) {
         switch (mContainerType) {
             case FOLDER:
-                mBorderSpace = new Point(deviceProfile.folderCellLayoutBorderSpacePx,
-                        deviceProfile.folderCellLayoutBorderSpacePx);
+                mBorderSpace = new Point(deviceProfile.folderCellLayoutBorderSpacePx);
                 break;
             case HOTSEAT:
                 mBorderSpace = new Point(deviceProfile.hotseatBorderSpace,
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 8d2f480..bd47fca 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -53,9 +53,14 @@
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.responsive.AllAppsSpecs;
+import com.android.launcher3.responsive.CalculatedAllAppsSpec;
+import com.android.launcher3.responsive.CalculatedFolderSpec;
+import com.android.launcher3.responsive.FolderSpecs;
 import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.IconSizeSteps;
 import com.android.launcher3.util.ResourceHelper;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.workspace.CalculatedWorkspaceSpec;
@@ -80,6 +85,7 @@
     public final InvariantDeviceProfile inv;
     private final Info mInfo;
     private final DisplayMetrics mMetrics;
+    private final IconSizeSteps mIconSizeSteps;
 
     // Device properties
     public final boolean isTablet;
@@ -112,6 +118,12 @@
     private WorkspaceSpecs mWorkspaceSpecs;
     private CalculatedWorkspaceSpec mResponsiveWidthSpec;
     private CalculatedWorkspaceSpec mResponsiveHeightSpec;
+    private AllAppsSpecs mAllAppsSpecs;
+    private CalculatedAllAppsSpec mAllAppsResponsiveWidthSpec;
+    private CalculatedAllAppsSpec mAllAppsResponsiveHeightSpec;
+    private FolderSpecs mFolderSpecs;
+    private CalculatedFolderSpec mResponsiveFolderWidthSpec;
+    private CalculatedFolderSpec mResponsiveFolderHeightSpec;
 
     /**
      * The maximum amount of left/right workspace padding as a percentage of the screen width.
@@ -170,7 +182,7 @@
     public int folderIconOffsetYPx;
 
     // Folder content
-    public int folderCellLayoutBorderSpacePx;
+    public Point folderCellLayoutBorderSpacePx;
     public int folderContentPaddingLeftRight;
     public int folderContentPaddingTop;
 
@@ -305,7 +317,9 @@
         mInsets.set(windowBounds.insets);
 
         // TODO(b/241386436): shouldn't change any launcher behaviour
-        mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE;
+        mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE
+                && inv.allAppsSpecsId != INVALID_RESOURCE_HANDLE
+                && inv.folderSpecsId != INVALID_RESOURCE_HANDLE;
 
         mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
         // Determine device posture.
@@ -323,6 +337,8 @@
         final Resources res = context.getResources();
         mMetrics = res.getDisplayMetrics();
 
+        mIconSizeSteps = mIsResponsiveGrid ? new IconSizeSteps(res) : null;
+
         // Determine sizes.
         widthPx = windowBounds.bounds.width();
         heightPx = windowBounds.bounds.height();
@@ -409,13 +425,15 @@
 
             folderContentPaddingTop = folderStyle.getDimensionPixelSize(
                     R.styleable.FolderStyle_folderTopPadding, 0);
-            folderCellLayoutBorderSpacePx = folderStyle.getDimensionPixelSize(
+
+            int gutter = folderStyle.getDimensionPixelSize(
                     R.styleable.FolderStyle_folderBorderSpace, 0);
+            folderCellLayoutBorderSpacePx = new Point(gutter, gutter);
             folderFooterHeightPx = folderStyle.getDimensionPixelSize(
                     R.styleable.FolderStyle_folderFooterHeight, 0);
             folderStyle.recycle();
-        } else {
-            folderCellLayoutBorderSpacePx = 0;
+        } else if (!mIsResponsiveGrid) {
+            folderCellLayoutBorderSpacePx = new Point(0, 0);
             folderFooterHeightPx = res.getDimensionPixelSize(R.dimen.folder_footer_height_default);
             folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_top_padding_default);
         }
@@ -528,12 +546,29 @@
         // for the available height to be correct
         if (mIsResponsiveGrid) {
             mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId));
+            int availableResponsiveWidth =
+                    availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0);
+            // don't use availableHeightPx because it subtracts bottom padding,
+            // but the workspace go behind it
+            int availableResponsiveHeight =
+                    heightPx - mInsets.top - (isVerticalBarLayout() ? 0 : hotseatBarSizePx);
             mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns,
-                    availableWidthPx);
+                    availableResponsiveWidth);
             mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows,
-                    // don't use availableHeightPx because it subtracts bottom padding,
-                    // but the hotseat go behind it
-                    heightPx - mInsets.top - hotseatBarSizePx);
+                    availableResponsiveHeight);
+
+            mAllAppsSpecs = new AllAppsSpecs(new ResourceHelper(context, inv.allAppsSpecsId));
+            mAllAppsResponsiveWidthSpec = mAllAppsSpecs.getCalculatedWidthSpec(inv.numColumns,
+                    mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
+            mAllAppsResponsiveHeightSpec = mAllAppsSpecs.getCalculatedHeightSpec(inv.numRows,
+                    mResponsiveHeightSpec.getAvailableSpace(),
+                    mResponsiveHeightSpec);
+
+            mFolderSpecs = new FolderSpecs(new ResourceHelper(context, inv.folderSpecsId));
+            mResponsiveFolderWidthSpec = mFolderSpecs.getWidthSpec(inv.numFolderColumns,
+                    mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
+            mResponsiveFolderHeightSpec = mFolderSpecs.getHeightSpec(inv.numFolderRows,
+                    mResponsiveHeightSpec.getAvailableSpace(), mResponsiveHeightSpec);
         }
 
         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
@@ -838,45 +873,6 @@
     }
 
     /**
-     * Re-computes the all-apps cell size to be independent of workspace
-     */
-    public void autoResizeAllAppsCells() {
-        int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
-        int topBottomPadding = textHeight;
-        allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
-                + textHeight + (topBottomPadding * 2);
-    }
-
-    private void updateAllAppsContainerWidth(Resources res) {
-        int cellLayoutHorizontalPadding =
-                (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2;
-        if (isTablet) {
-            int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
-                    + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
-                    + allAppsLeftRightPadding * 2;
-            allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2);
-        } else {
-            allAppsLeftRightPadding =
-                    Math.max(0, desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding
-                            - (allAppsBorderSpacePx.x / 2));
-        }
-    }
-
-    private void setupAllAppsStyle(Context context) {
-        TypedArray allAppsStyle;
-        if (inv.allAppsStyle != INVALID_RESOURCE_HANDLE) {
-            allAppsStyle = context.obtainStyledAttributes(inv.allAppsStyle,
-                    R.styleable.AllAppsStyle);
-        } else {
-            allAppsStyle = context.obtainStyledAttributes(R.style.AllAppsStyleDefault,
-                    R.styleable.AllAppsStyle);
-        }
-        allAppsLeftRightPadding = allAppsStyle.getDimensionPixelSize(
-                R.styleable.AllAppsStyle_horizontalPadding, 0);
-        allAppsStyle.recycle();
-    }
-
-    /**
      * Returns the amount of extra (or unused) vertical space.
      */
     private int updateAvailableDimensions(Resources res) {
@@ -951,9 +947,32 @@
 
             cellWidthPx = mResponsiveWidthSpec.getCellSizePx();
             cellHeightPx = mResponsiveHeightSpec.getCellSizePx();
-            cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
 
-            // TODO(b/283929701): decrease icon size if content doesn't fit on cell
+            if (cellWidthPx < iconSizePx) {
+                // get a smaller icon size
+                iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx);
+                // calculate new cellContentHeight
+                cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
+            }
+
+            while (iconSizePx > mIconSizeSteps.minimumIconSize()
+                    && cellContentHeight > cellHeightPx) {
+                int extraHeightRequired = cellContentHeight - cellHeightPx;
+                int newPadding = iconDrawablePaddingPx - extraHeightRequired;
+                if (newPadding >= 0) {
+                    // Responsive uses the padding without scaling
+                    iconDrawablePaddingPx = iconDrawablePaddingOriginalPx = newPadding;
+                    cellTextAndPaddingHeight =
+                            iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
+                } else {
+                    // get a smaller icon size
+                    iconSizePx = mIconSizeSteps.getNextLowerIconSize(iconSizePx);
+                }
+                // calculate new cellContentHeight
+                cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
+            }
+
+            cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
         } else if (mIsScalableGrid) {
             cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
             cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale);
@@ -1027,7 +1046,15 @@
         }
 
         // All apps
-        updateAllAppsIconSize(scale, res);
+        if (mIsResponsiveGrid) {
+            updateAllAppsWithResponsiveMeasures();
+        } else {
+            updateAllAppsIconSize(scale, res);
+        }
+        updateAllAppsContainerWidth();
+        if (isVerticalBarLayout()) {
+            hideWorkspaceLabelsIfNotEnoughSpace();
+        }
 
         updateHotseatSizes(iconSizePx);
 
@@ -1113,22 +1140,71 @@
                     res.getDimensionPixelSize(R.dimen.all_apps_icon_drawable_padding);
             allAppsCellWidthPx = allAppsIconSizePx + (2 * allAppsIconDrawablePaddingPx);
         }
+    }
 
-        updateAllAppsContainerWidth(res);
-        if (isVerticalBarLayout()) {
-            hideWorkspaceLabelsIfNotEnoughSpace();
+    private void updateAllAppsWithResponsiveMeasures() {
+        allAppsIconSizePx = iconSizePx;
+        allAppsIconTextSizePx = iconTextSizePx;
+        allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+
+        allAppsBorderSpacePx = new Point(
+                mAllAppsResponsiveWidthSpec.getGutterPx(),
+                mAllAppsResponsiveHeightSpec.getGutterPx()
+        );
+        allAppsCellHeightPx = mAllAppsResponsiveHeightSpec.getCellSizePx()
+                + mAllAppsResponsiveHeightSpec.getGutterPx();
+        allAppsCellWidthPx = mAllAppsResponsiveWidthSpec.getCellSizePx();
+        allAppsLeftRightPadding = mAllAppsResponsiveWidthSpec.getStartPaddingPx();
+    }
+
+    /**
+     * Re-computes the all-apps cell size to be independent of workspace
+     */
+    public void autoResizeAllAppsCells() {
+        int textHeight = Utilities.calculateTextHeight(allAppsIconTextSizePx);
+        int topBottomPadding = textHeight;
+        allAppsCellHeightPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx
+                + textHeight + (topBottomPadding * 2);
+    }
+
+    private void updateAllAppsContainerWidth() {
+        int cellLayoutHorizontalPadding =
+                (cellLayoutPaddingPx.left + cellLayoutPaddingPx.right) / 2;
+        if (isTablet) {
+            int usedWidth = (allAppsCellWidthPx * numShownAllAppsColumns)
+                    + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
+                    + allAppsLeftRightPadding * 2;
+            allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2);
+        } else {
+            allAppsLeftRightPadding =
+                    Math.max(0, desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding
+                            - (allAppsBorderSpacePx.x / 2));
         }
     }
 
+    private void setupAllAppsStyle(Context context) {
+        TypedArray allAppsStyle = context.obtainStyledAttributes(
+                inv.allAppsStyle != INVALID_RESOURCE_HANDLE ? inv.allAppsStyle
+                        : R.style.AllAppsStyleDefault, R.styleable.AllAppsStyle);
+
+        allAppsLeftRightPadding = allAppsStyle.getDimensionPixelSize(
+                R.styleable.AllAppsStyle_horizontalPadding, 0);
+        allAppsStyle.recycle();
+    }
+
+    // TODO(b/288075868): Resize the icon size to make sure it will fit inside the cell size
     private void updateAvailableFolderCellDimensions(Resources res) {
         updateFolderCellSize(1f, res);
 
+        // Responsive grid doesn't need to scale the folder
+        if (mIsResponsiveGrid) return;
+
         // For usability we can't have the folder use the whole width of the screen
         Point totalWorkspacePadding = getTotalWorkspacePadding();
 
         // Check if the folder fit within the available height.
         float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
-                + ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacePx)
+                + ((inv.numFolderRows - 1) * folderCellLayoutBorderSpacePx.y)
                 + folderFooterHeightPx
                 + folderContentPaddingTop;
         int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y;
@@ -1136,7 +1212,7 @@
 
         // Check if the folder fit within the available width.
         float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
-                + ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacePx)
+                + ((inv.numFolderColumns - 1) * folderCellLayoutBorderSpacePx.x)
                 + folderContentPaddingLeftRight * 2;
         int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x;
         float scaleX = contentMaxWidth / contentUsedWidth;
@@ -1156,7 +1232,19 @@
 
         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
 
-        if (mIsScalableGrid) {
+        if (mIsResponsiveGrid) {
+            folderCellWidthPx = mResponsiveFolderWidthSpec.getCellSizePx();
+
+            // Height
+            folderCellHeightPx = mResponsiveFolderHeightSpec.getCellSizePx();
+            folderContentPaddingTop = mResponsiveFolderHeightSpec.getStartPaddingPx();
+            folderFooterHeightPx = mResponsiveFolderHeightSpec.getEndPaddingPx();
+
+            folderCellLayoutBorderSpacePx = new Point(mResponsiveFolderWidthSpec.getGutterPx(),
+                    mResponsiveHeightSpec.getGutterPx());
+
+            folderContentPaddingLeftRight = mResponsiveFolderWidthSpec.getStartPaddingPx();
+        } else if (mIsScalableGrid) {
             if (inv.folderStyle == INVALID_RESOURCE_HANDLE) {
                 folderCellWidthPx = roundPxValueFromFloat(getCellSize().x * scale);
                 folderCellHeightPx = roundPxValueFromFloat(getCellSize().y * scale);
@@ -1166,11 +1254,13 @@
             }
 
             folderContentPaddingTop = roundPxValueFromFloat(folderContentPaddingTop * scale);
-            folderCellLayoutBorderSpacePx = roundPxValueFromFloat(
-                    folderCellLayoutBorderSpacePx * scale);
+            folderCellLayoutBorderSpacePx = new Point(
+                    roundPxValueFromFloat(folderCellLayoutBorderSpacePx.x * scale),
+                    roundPxValueFromFloat(folderCellLayoutBorderSpacePx.y * scale)
+            );
             folderFooterHeightPx = roundPxValueFromFloat(folderFooterHeightPx * scale);
 
-            folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx;
+            folderContentPaddingLeftRight = folderCellLayoutBorderSpacePx.x;
         } else {
             int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding)
                     * scale);
@@ -1691,8 +1781,10 @@
         writer.println(prefix + pxToDpStr("folderChildTextSizePx", folderChildTextSizePx));
         writer.println(prefix + pxToDpStr("folderChildDrawablePaddingPx",
                 folderChildDrawablePaddingPx));
-        writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx",
-                folderCellLayoutBorderSpacePx));
+        writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx.x",
+                folderCellLayoutBorderSpacePx.x));
+        writer.println(prefix + pxToDpStr("folderCellLayoutBorderSpacePx.y",
+                folderCellLayoutBorderSpacePx.y));
         writer.println(prefix + pxToDpStr("folderContentPaddingLeftRight",
                 folderContentPaddingLeftRight));
         writer.println(prefix + pxToDpStr("folderTopPadding", folderContentPaddingTop));
@@ -1808,6 +1900,12 @@
         if (mIsResponsiveGrid) {
             writer.println(prefix + "\tmResponsiveHeightSpec:" + mResponsiveHeightSpec.toString());
             writer.println(prefix + "\tmResponsiveWidthSpec:" + mResponsiveWidthSpec.toString());
+            writer.println(prefix + "\tmAllAppsResponsiveHeightSpec:"
+                    + mAllAppsResponsiveHeightSpec.toString());
+            writer.println(prefix + "\tmAllAppsResponsiveWidthSpec:"
+                    + mAllAppsResponsiveWidthSpec.toString());
+            writer.println(prefix + "\tmResponsiveFolderHeightSpec:" + mResponsiveFolderHeightSpec);
+            writer.println(prefix + "\tmResponsiveFolderWidthSpec:" + mResponsiveFolderWidthSpec);
         }
     }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 7eb085a..4eaacdc 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -179,6 +179,10 @@
     public int devicePaddingId = INVALID_RESOURCE_HANDLE;
     @XmlRes
     public int workspaceSpecsId = INVALID_RESOURCE_HANDLE;
+    @XmlRes
+    public int allAppsSpecsId = INVALID_RESOURCE_HANDLE;
+    @XmlRes
+    public int folderSpecsId = INVALID_RESOURCE_HANDLE;
 
     public String dbFile;
     public int defaultLayoutId;
@@ -353,6 +357,8 @@
         isScalable = closestProfile.isScalable;
         devicePaddingId = closestProfile.devicePaddingId;
         workspaceSpecsId = closestProfile.mWorkspaceSpecsId;
+        allAppsSpecsId = closestProfile.mAllAppsSpecsId;
+        folderSpecsId = closestProfile.mFolderSpecsId;
         this.deviceType = deviceType;
 
         inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -799,6 +805,8 @@
         private final boolean isScalable;
         private final int devicePaddingId;
         private final int mWorkspaceSpecsId;
+        private final int mAllAppsSpecsId;
+        private final int mFolderSpecsId;
 
         public GridOption(Context context, AttributeSet attrs) {
             TypedArray a = context.obtainStyledAttributes(
@@ -863,8 +871,14 @@
             if (FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE.get()) {
                 mWorkspaceSpecsId = a.getResourceId(
                         R.styleable.GridDisplayOption_workspaceSpecsId, INVALID_RESOURCE_HANDLE);
+                mAllAppsSpecsId = a.getResourceId(
+                        R.styleable.GridDisplayOption_allAppsSpecsId, INVALID_RESOURCE_HANDLE);
+                mFolderSpecsId = a.getResourceId(
+                        R.styleable.GridDisplayOption_folderSpecsId, INVALID_RESOURCE_HANDLE);
             } else {
                 mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
+                mAllAppsSpecsId = INVALID_RESOURCE_HANDLE;
+                mFolderSpecsId = INVALID_RESOURCE_HANDLE;
             }
 
             int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bf375d7..7e43002 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1065,6 +1065,7 @@
         logStopAndResume(false /* isResume */);
         mAppWidgetHolder.setActivityStarted(false);
         NotificationListener.removeNotificationsChangedListener(getPopupDataProvider());
+        FloatingIconView.resetIconLoadResult();
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 51abfd3..c20f323 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -30,7 +30,6 @@
 import android.widget.TextView;
 
 import com.android.launcher3.util.MultiScalePropertyFactory;
-import com.android.launcher3.views.ScrimView;
 
 public class LauncherAnimUtils {
     /**
@@ -195,10 +194,6 @@
 
                 @Override
                 public Integer get(View view) {
-                    if (view instanceof ScrimView) {
-                        return ((ScrimView) view).getBackgroundColor();
-                    }
-
                     if (!(view.getBackground() instanceof ColorDrawable)) {
                         return Color.TRANSPARENT;
                     }
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index ba6dc26..f921d1d 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -162,7 +162,7 @@
             // No need to add padding when cell layout border spacing is present.
             boolean noPaddingX =
                     (dp.cellLayoutBorderSpacePx.x > 0 && mContainerType == WORKSPACE)
-                            || (dp.folderCellLayoutBorderSpacePx > 0 && mContainerType == FOLDER)
+                            || (dp.folderCellLayoutBorderSpacePx.x > 0 && mContainerType == FOLDER)
                             || (dp.hotseatBorderSpace > 0 && mContainerType == HOTSEAT);
             int cellPaddingX = noPaddingX
                     ? 0
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 1404a92..e4f34ae 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -199,7 +199,7 @@
         propertySetter.setFloat(sysUiScrim.getSysUIProgress(), AnimatedFloat.VALUE,
                 state.hasFlag(FLAG_HAS_SYS_UI_SCRIM) ? 1 : 0, LINEAR);
 
-        propertySetter.setScrimViewBackgroundColor(mLauncher.getScrimView(),
+        propertySetter.setViewBackgroundColor(mLauncher.getScrimView(),
                 state.getWorkspaceScrimColor(mLauncher),
                 config.getInterpolator(ANIM_SCRIM_FADE, ACCELERATE_2));
     }
diff --git a/src/com/android/launcher3/anim/AnimatedPropertySetter.java b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
index ff6bdec..82e645a 100644
--- a/src/com/android/launcher3/anim/AnimatedPropertySetter.java
+++ b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
@@ -24,14 +24,13 @@
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
+import android.graphics.drawable.ColorDrawable;
 import android.util.FloatProperty;
 import android.util.IntProperty;
 import android.view.View;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.views.ScrimView;
-
 import java.util.function.Consumer;
 
 /**
@@ -63,9 +62,9 @@
     }
 
     @Override
-    public Animator setScrimViewBackgroundColor(ScrimView view, int color,
-            TimeInterpolator interpolator) {
-        if (view == null || view.getBackgroundColor() == color) {
+    public Animator setViewBackgroundColor(View view, int color, TimeInterpolator interpolator) {
+        if (view == null || (view.getBackground() instanceof ColorDrawable
+                && ((ColorDrawable) view.getBackground()).getColor() == color)) {
             return NO_OP;
         }
         ObjectAnimator anim = ObjectAnimator.ofArgb(view, VIEW_BACKGROUND_COLOR, color);
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
index 0b2858d..6ba58b6 100644
--- a/src/com/android/launcher3/anim/PropertySetter.java
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -25,8 +25,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.views.ScrimView;
-
 import java.util.function.Consumer;
 
 /**
@@ -64,8 +62,7 @@
      * Sets the background color of the provided view using the provided interpolator.
      */
     @NonNull
-    public Animator setScrimViewBackgroundColor(ScrimView view, int color,
-            TimeInterpolator interpolator) {
+    public Animator setViewBackgroundColor(View view, int color, TimeInterpolator interpolator) {
         if (view != null) {
             view.setBackgroundColor(color);
         }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 02bbd24..df24620 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -267,7 +267,7 @@
                     + " is enabled or in prefix state");
 
     public static final BooleanFlag ENABLE_SEARCH_UNINSTALLED_APPS = getReleaseFlag(270395269,
-            "ENABLE_SEARCH_UNINSTALLED_APPS", DISABLED, "Search uninstalled app results.");
+            "ENABLE_SEARCH_UNINSTALLED_APPS", ENABLED, "Search uninstalled app results.");
 
     // TODO(Block 20): Clean up flags
     public static final BooleanFlag ENABLE_SCRIM_FOR_APP_LAUNCH = getDebugFlag(270393276,
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index dd82ecf..b09985c 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -236,9 +236,9 @@
                 mFolder, startRect, endRect, finalRadius, !mIsOpening));
 
         // Create reveal animator for the folder content (capture the top 4 icons 2x2)
-        int width = mDeviceProfile.folderCellLayoutBorderSpacePx
+        int width = mDeviceProfile.folderCellLayoutBorderSpacePx.x
                 + mDeviceProfile.folderCellWidthPx * 2;
-        int height = mDeviceProfile.folderCellLayoutBorderSpacePx
+        int height = mDeviceProfile.folderCellLayoutBorderSpacePx.y
                 + mDeviceProfile.folderCellHeightPx * 2;
         int page = mIsOpening ? mContent.getCurrentPage() : mContent.getDestinationPage();
         int left = mContent.getPaddingLeft() + page * lp.width;
diff --git a/src/com/android/launcher3/responsive/FolderSpecs.kt b/src/com/android/launcher3/responsive/FolderSpecs.kt
index be97cf8..f4446bc 100644
--- a/src/com/android/launcher3/responsive/FolderSpecs.kt
+++ b/src/com/android/launcher3/responsive/FolderSpecs.kt
@@ -188,12 +188,12 @@
 }
 
 data class CalculatedFolderSpec(
+    val availableSpace: Int,
+    val cells: Int,
     val startPaddingPx: Int,
     val endPaddingPx: Int,
     val gutterPx: Int,
-    val cellSizePx: Int,
-    val availableSpace: Int,
-    val cells: Int
+    val cellSizePx: Int
 )
 
 /**
@@ -270,11 +270,11 @@
     cellSizePx = folderSpec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx)
 
     return CalculatedFolderSpec(
+        availableSpace = availableSpace,
+        cells = cells,
         startPaddingPx = startPaddingPx,
         endPaddingPx = endPaddingPx,
         gutterPx = gutterPx,
-        cellSizePx = cellSizePx,
-        availableSpace = availableSpace,
-        cells = cells
+        cellSizePx = cellSizePx
     )
 }
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 520f33c..de5887f 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -237,4 +237,10 @@
      * @param leftOrTop if the staged split will be positioned left or top.
      */
     public void enterStageSplitFromRunningApp(boolean leftOrTop) { }
+
+
+    /** Returns whether the overview command helper queue is empty. */
+    public boolean isCommandQueueEmpty() {
+        return true;
+    }
 }
diff --git a/src/com/android/launcher3/util/IconSizeSteps.kt b/src/com/android/launcher3/util/IconSizeSteps.kt
new file mode 100644
index 0000000..2a5afe0
--- /dev/null
+++ b/src/com/android/launcher3/util/IconSizeSteps.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util
+
+import android.content.res.Resources
+import androidx.core.content.res.getDimensionOrThrow
+import androidx.core.content.res.use
+import com.android.launcher3.R
+import kotlin.math.max
+
+class IconSizeSteps(res: Resources) {
+    private val steps: List<Int>
+
+    init {
+        steps =
+            res.obtainTypedArray(R.array.icon_size_steps).use {
+                (0 until it.length()).map { step -> it.getDimensionOrThrow(step).toInt() }.sorted()
+            }
+    }
+
+    fun minimumIconSize(): Int = steps[0]
+
+    fun getNextLowerIconSize(iconSizePx: Int): Int {
+        return steps[max(0, getIndexForIconSize(iconSizePx) - 1)]
+    }
+
+    fun getIconSmallerThan(cellWidth: Int): Int {
+        return steps.lastOrNull { it <= cellWidth } ?: steps[0]
+    }
+
+    private fun getIndexForIconSize(iconSizePx: Int): Int {
+        return max(0, steps.indexOfFirst { iconSizePx <= it })
+    }
+}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 3b05221..f425821 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -572,6 +572,13 @@
     }
 
     /**
+     * Resets the static icon load result used for preloading the icon for a launching app.
+     */
+    public static void resetIconLoadResult() {
+        sIconLoadResult = null;
+    }
+
+    /**
      * Creates a floating icon view for {@param originalView}.
      * @param originalView The view to copy
      * @param visibilitySyncView A view whose visibility should update in sync with originalView.
@@ -608,7 +615,7 @@
             }
             view.setOriginalDrawableBackground(view.mIconLoadResult.btvDrawable);
         }
-        sIconLoadResult = null;
+        resetIconLoadResult();
 
         // Match the position of the original view.
         view.matchPositionOf(launcher, originalView, isOpening, positionOut);
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 6a9aedf..ca80c51 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -21,6 +21,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
 import android.view.View;
 
@@ -62,16 +63,18 @@
     }
 
     @Override
+    protected boolean onSetAlpha(int alpha) {
+        updateSysUiColors();
+        dispatchVisibilityListenersIfNeeded();
+        return super.onSetAlpha(alpha);
+    }
+
+    @Override
     public void setBackgroundColor(int color) {
         mBackgroundColor = color;
-        dispatchVisibilityListenersIfNeeded();
-        if (Color.alpha(color) == 0) {
-            setAlpha(0);
-        } else {
-            setAlpha(1);
-            super.setBackgroundColor(color);
-        }
         updateSysUiColors();
+        dispatchVisibilityListenersIfNeeded();
+        super.setBackgroundColor(color);
     }
 
     public int getBackgroundColor() {
@@ -86,7 +89,7 @@
     }
 
     public boolean isFullyOpaque() {
-        return mIsVisible && getAlpha() == 1;
+        return mIsVisible && getAlpha() == 1 && Color.alpha(mBackgroundColor) == 255;
     }
 
     @Override
@@ -117,7 +120,8 @@
         // status bar.
         final float threshold = STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD;
         boolean forceChange = getVisibility() == VISIBLE
-                && getAlpha() > threshold;
+                && getAlpha() > threshold
+                && (Color.alpha(mBackgroundColor) / 255f) > threshold;
         if (forceChange) {
             getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !isScrimDark());
         } else {
@@ -144,7 +148,13 @@
     }
 
     private boolean isScrimDark() {
-        return ColorUtils.calculateLuminance(mBackgroundColor) < 0.5f;
+        if (!(getBackground() instanceof ColorDrawable)) {
+            throw new IllegalStateException(
+                    "ScrimView must have a ColorDrawable background, this one has: "
+                            + getBackground());
+        }
+        return ColorUtils.calculateLuminance(
+                ((ColorDrawable) getBackground()).getColor()) < 0.5f;
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
index 3c2b49a..28899d9 100644
--- a/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
+++ b/tests/src/com/android/launcher3/celllayout/CellLayoutBoard.java
@@ -103,6 +103,8 @@
         public static final char IGNORE = 'x';
         // The cells marked by this will be filled by app icons
         public static final char ICON = 'i';
+        // The cells marked by FOLDER will be filled by folders with 27 app icons inside
+        public static final char FOLDER = 'Z';
         // Empty space
         public static final char EMPTY = '-';
         // Widget that will be saved as "main widget" for easier retrieval
@@ -171,6 +173,25 @@
         }
     }
 
+    public static class FolderPoint {
+        public Point coord;
+        public char mType;
+
+        public FolderPoint(Point coord, char type) {
+            this.coord = coord;
+            mType = type;
+        }
+
+        /**
+         * [A-Z]: Represents a folder and number of icons in the folder is represented by
+         * the order of letter in the alphabet, A=2, B=3, C=4 ... etc.
+         */
+        public int getNumberIconsInside() {
+            return (mType - 'A') + 2;
+        }
+    }
+
+
     private HashSet<Character> mUsedWidgetTypes = new HashSet<>();
 
     static final int INFINITE = 99999;
@@ -181,6 +202,7 @@
     Map<Character, WidgetRect> mWidgetsMap = new HashMap<>();
 
     List<IconPoint> mIconPoints = new ArrayList<>();
+    List<FolderPoint> mFolderPoints = new ArrayList<>();
 
     WidgetRect mMain = null;
 
@@ -213,6 +235,10 @@
         return mIconPoints;
     }
 
+    public List<FolderPoint> getFolders() {
+        return mFolderPoints;
+    }
+
     public WidgetRect getMain() {
         return mMain;
     }
@@ -248,6 +274,17 @@
             }
             return true;
         }).collect(Collectors.toList());
+
+        // Remove overlapping folders and remove them from the board
+        mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
+            int x = folderPoint.coord.x;
+            int y = folderPoint.coord.y;
+            if (rect.contains(x, y)) {
+                mWidget[x][y] = '-';
+                return false;
+            }
+            return true;
+        }).collect(Collectors.toList());
     }
 
     private void removeOverlappingItems(Point p) {
@@ -269,6 +306,17 @@
             }
             return true;
         }).collect(Collectors.toList());
+
+        // Remove overlapping folders and remove them from the board
+        mFolderPoints = mFolderPoints.stream().filter(folderPoint -> {
+            int x = folderPoint.coord.x;
+            int y = folderPoint.coord.y;
+            if (p.x == x && p.y == y) {
+                mWidget[x][y] = '-';
+                return false;
+            }
+            return true;
+        }).collect(Collectors.toList());
     }
 
     private char getNextWidgetType() {
@@ -373,6 +421,18 @@
         return iconPoints;
     }
 
+    private static List<FolderPoint> getFolderPoints(char[][] board) {
+        List<FolderPoint> folderPoints = new ArrayList<>();
+        for (int x = 0; x < board.length; x++) {
+            for (int y = 0; y < board[0].length; y++) {
+                if (isFolder(board[x][y])) {
+                    folderPoints.add(new FolderPoint(new Point(x, y), board[x][y]));
+                }
+            }
+        }
+        return folderPoints;
+    }
+
     public static WidgetRect getMainFromList(List<CellLayoutBoard> boards) {
         for (CellLayoutBoard board : boards) {
             WidgetRect main = board.getMain();
@@ -406,6 +466,7 @@
             board.mWidgetsMap.put(widgetRect.mType, widgetRect);
         });
         board.mIconPoints = getIconPoints(board.mWidget);
+        board.mFolderPoints = getFolderPoints(board.mWidget);
         return board;
     }
 
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index 6f6a443..bf7a21c 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
 import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -73,12 +74,31 @@
             // Add new data
             try (SQLiteTransaction transaction = controller.newTransaction()) {
                 int count = mItemsToSubmit.size();
+                ArrayList<ItemInfo> containerItems = new ArrayList<>();
                 for (int i = 0; i < count; i++) {
                     ContentWriter writer = new ContentWriter(mContext);
-                    mItemsToSubmit.get(i).get().onAddToDatabase(writer);
+                    ItemInfo item = mItemsToSubmit.get(i).get();
+
+                    if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+                        FolderInfo folderInfo = (FolderInfo) item;
+                        for (ItemInfo itemInfo : folderInfo.contents) {
+                            itemInfo.container = i;
+                            containerItems.add(itemInfo);
+                        }
+                    }
+
+                    item.onAddToDatabase(writer);
                     writer.put(LauncherSettings.Favorites._ID, i);
                     controller.insert(TABLE_NAME, writer.getValues(mContext));
                 }
+
+                for (int i = 0; i < containerItems.size(); i++) {
+                    ContentWriter writer = new ContentWriter(mContext);
+                    ItemInfo item = containerItems.get(i);
+                    item.onAddToDatabase(writer);
+                    writer.put(LauncherSettings.Favorites._ID, count + i);
+                    controller.insert(TABLE_NAME, writer.getValues(mContext));
+                }
                 transaction.commit();
             }
         });
diff --git a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
index 8d06e33..398bd82 100644
--- a/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
+++ b/tests/src/com/android/launcher3/celllayout/TestWorkspaceBuilder.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -102,6 +103,9 @@
         board.getIcons().forEach((iconPoint) ->
                 transaction.addItem(() -> createIconInCell(iconPoint, screenId))
         );
+        board.getFolders().forEach((folderPoint) ->
+                transaction.addItem(() -> createFolderInCell(folderPoint, screenId))
+        );
         return transaction;
     }
 
@@ -130,6 +134,30 @@
         };
     }
 
+    public FolderInfo createFolderInCell(CellLayoutBoard.FolderPoint folderPoint, int screenId) {
+        FolderInfo folderInfo = new FolderInfo();
+        folderInfo.screenId = screenId;
+        folderInfo.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+        folderInfo.cellX = folderPoint.coord.x;
+        folderInfo.cellY = folderPoint.coord.y;
+        folderInfo.minSpanY = folderInfo.minSpanX = folderInfo.spanX = folderInfo.spanY = 1;
+        folderInfo.setOption(FolderInfo.FLAG_MULTI_PAGE_ANIMATION, true, null);
+
+        for (int i = 0; i < folderPoint.getNumberIconsInside(); i++) {
+            folderInfo.add(getDefaultWorkspaceItem(screenId), false);
+        }
+
+        return folderInfo;
+    }
+
+    private WorkspaceItemInfo getDefaultWorkspaceItem(int screenId) {
+        WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
+        item.screenId = screenId;
+        item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
+        item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+        return item;
+    }
+
     private ItemInfo createIconInCell(CellLayoutBoard.IconPoint iconPoint, int screenId) {
         WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
         item.screenId = screenId;
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index c91a4a9..0a95771 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -84,7 +84,8 @@
                     "\tfolderChildIconSizePx: 147.0px (56.0dp)\n" +
                     "\tfolderChildTextSizePx: 38.0px (14.476191dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
                     "\tfolderTopPadding: 63.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -220,7 +221,8 @@
                     "\tfolderChildIconSizePx: 147.0px (56.0dp)\n" +
                     "\tfolderChildTextSizePx: 38.0px (14.476191dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
                     "\tfolderTopPadding: 63.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -356,7 +358,8 @@
                     "\tfolderChildIconSizePx: 131.0px (49.904762dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 9.0px (3.4285715dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
                     "\tfolderTopPadding: 56.0px (21.333334dp)\n" +
                     "\tfolderFooterHeight: 131.0px (49.904762dp)\n" +
@@ -492,7 +495,8 @@
                     "\tfolderChildIconSizePx: 123.0px (46.857143dp)\n" +
                     "\tfolderChildTextSizePx: 32.0px (12.190476dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 8.0px (3.047619dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
                     "\tfolderTopPadding: 53.0px (20.190475dp)\n" +
                     "\tfolderFooterHeight: 123.0px (46.857143dp)\n" +
@@ -629,7 +633,8 @@
                     "\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
                     "\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
                     "\tfolderTopPadding: 48.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 112.0px (56.0dp)\n" +
@@ -766,7 +771,8 @@
                     "\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
                     "\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 16.0px (8.0dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
                     "\tfolderTopPadding: 48.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 112.0px (56.0dp)\n" +
@@ -903,7 +909,8 @@
                     "\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
                     "\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
                     "\tfolderTopPadding: 48.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 112.0px (56.0dp)\n" +
@@ -1040,7 +1047,8 @@
                     "\tfolderChildIconSizePx: 120.0px (60.0dp)\n" +
                     "\tfolderChildTextSizePx: 28.0px (14.0dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 27.0px (13.5dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 0.0px (0.0dp)\n" +
                     "\tfolderTopPadding: 48.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 112.0px (56.0dp)\n" +
@@ -1182,7 +1190,8 @@
                     "\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
                     "\tfolderTopPadding: 63.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -1323,7 +1332,8 @@
                     "\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
                     "\tfolderTopPadding: 63.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -1464,7 +1474,8 @@
                     "\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
                     "\tfolderTopPadding: 63.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 147.0px (56.0dp)\n" +
@@ -1601,7 +1612,8 @@
                     "\tfolderChildIconSizePx: 141.0px (53.714287dp)\n" +
                     "\tfolderChildTextSizePx: 34.0px (12.952381dp)\n" +
                     "\tfolderChildDrawablePaddingPx: 10.0px (3.8095238dp)\n" +
-                    "\tfolderCellLayoutBorderSpacePx: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)\n" +
+                    "\tfolderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)\n" +
                     "\tfolderContentPaddingLeftRight: 21.0px (8.0dp)\n" +
                     "\tfolderTopPadding: 63.0px (24.0dp)\n" +
                     "\tfolderFooterHeight: 147.0px (56.0dp)\n" +
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index e427560..095b135 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -30,7 +30,6 @@
 import android.content.Intent;
 import android.graphics.Point;
 import android.os.SystemClock;
-import android.platform.test.annotations.IwTest;
 import android.platform.test.annotations.PlatinumTest;
 import android.util.Log;
 
@@ -240,7 +239,6 @@
     }
 
     @PlatinumTest(focusArea = "launcher")
-    @IwTest(focusArea = "launcher")
     @Test
     @ScreenRecord // b/202433017
     public void testWorkspace() throws Exception {
@@ -375,7 +373,6 @@
     }
 
     @PlatinumTest(focusArea = "launcher")
-    @IwTest(focusArea = "launcher")
     @Test
     @PortraitLandscape
     @ScreenRecord // b/256898879
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index b869c9f..435649b 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -20,7 +20,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.platform.test.annotations.IwTest;
 import android.platform.test.annotations.PlatinumTest;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -52,7 +51,6 @@
     public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
 
     @PlatinumTest(focusArea = "launcher")
-    @IwTest(focusArea="launcher")
     @Test
     @PortraitLandscape
     public void testDragIcon() throws Throwable {
diff --git a/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt b/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt
new file mode 100644
index 0000000..d9e3377
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/IconSizeStepsTest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.res.Configuration
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class IconSizeStepsTest {
+    private var context: Context? = null
+    private val runningContext: Context = ApplicationProvider.getApplicationContext()
+    private lateinit var iconSizeSteps: IconSizeSteps
+
+    @Before
+    fun setup() {
+        // 160dp makes 1px = 1dp
+        val config =
+            Configuration(runningContext.resources.configuration).apply { this.densityDpi = 160 }
+        context = runningContext.createConfigurationContext(config)
+        iconSizeSteps = IconSizeSteps(context!!.resources)
+    }
+
+    @Test
+    fun minimumIconSize() {
+        assertThat(iconSizeSteps.minimumIconSize()).isEqualTo(52)
+    }
+
+    @Test
+    fun getNextLowerIconSize() {
+        assertThat(iconSizeSteps.getNextLowerIconSize(66)).isEqualTo(63)
+
+        // Assert that never goes below minimum
+        assertThat(iconSizeSteps.getNextLowerIconSize(52)).isEqualTo(52)
+        assertThat(iconSizeSteps.getNextLowerIconSize(30)).isEqualTo(52)
+    }
+
+    @Test
+    fun getIconSmallerThan() {
+        assertThat(iconSizeSteps.getIconSmallerThan(60)).isEqualTo(59)
+
+        // Assert that never goes below minimum
+        assertThat(iconSizeSteps.getIconSmallerThan(52)).isEqualTo(52)
+        assertThat(iconSizeSteps.getIconSmallerThan(30)).isEqualTo(52)
+    }
+}