Merge "Folder preview work badge theming fix" into main
diff --git a/quickstep/res/layout/taskbar_divider_popup_menu.xml b/quickstep/res/layout/taskbar_divider_popup_menu.xml
index 4348a47..6fbb586 100644
--- a/quickstep/res/layout/taskbar_divider_popup_menu.xml
+++ b/quickstep/res/layout/taskbar_divider_popup_menu.xml
@@ -38,6 +38,7 @@
         android:theme="@style/PopupItem">
 
         <View
+            android:id="@+id/taskbar_pinning_visibility_icon"
             android:layout_margin="6dp"
             android:layout_width="20dp"
             android:layout_height="20dp"
@@ -45,13 +46,17 @@
             android:backgroundTint="?android:attr/textColorPrimary" />
 
         <Switch
-            style="@style/BaseIcon"
+            style="@style/Switch.SettingsLib"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:id="@+id/taskbar_pinning_switch"
             android:background="@null"
             android:clickable="false"
             android:gravity="start|center_vertical"
             android:textAlignment="viewStart"
             android:paddingStart="12dp"
+            android:layout_weight="1"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
             android:singleLine="true"
             android:ellipsize="end"
             android:textSize="14sp"
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index aaa699b..22f98fa 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -386,6 +386,7 @@
 
     <!--- Taskbar Pinning -->
     <dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
+    <dimen name="taskbar_pinning_popup_menu_vertical_margin">16dp</dimen>
 
     <!-- Recents overview -->
     <dimen name="recents_filter_icon_size">30dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index be4426d..a832f75 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -734,7 +734,7 @@
         // TODO(b/244231596) we're getting the incorrect kidsMode value in small-screen
         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
 
-        if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
+        if (TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
             NavButtonLayoutter navButtonLayoutter =
                     NavButtonLayoutFactory.Companion.getUiLayoutter(
                             dp, mNavButtonsView, mImeSwitcherButton,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 6ee151b..1d949e2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -35,7 +35,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_FULLSCREEN;
-import static com.android.launcher3.taskbar.TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW;
+import static com.android.launcher3.taskbar.TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.testing.shared.ResourceUtils.getBoolByName;
 import static com.android.launcher3.util.VibratorWrapper.EFFECT_CLICK;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
@@ -354,7 +354,7 @@
                 sharedState.systemBarAttrsBehavior);
         onNavButtonsDarkIntensityChanged(sharedState.navButtonsDarkIntensity);
 
-        if (FLAG_HIDE_NAVBAR_WINDOW) {
+        if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
             // W/ the flag not set this entire class gets re-created, which resets the value of
             // mIsDestroyed. We re-use the class for small-screen, so we explicitly have to mark
             // this class as non-destroyed
@@ -454,7 +454,7 @@
      */
     private WindowManager.LayoutParams createAllWindowParams() {
         final int windowType =
-                FLAG_HIDE_NAVBAR_WINDOW ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
+                ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
         WindowManager.LayoutParams windowLayoutParams =
                 createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE);
         boolean isPhoneNavMode = TaskbarManager.isPhoneButtonNavMode(this);
@@ -719,7 +719,7 @@
         mIsDestroyed = true;
         setUIController(TaskbarUIController.DEFAULT);
         mControllers.onDestroy();
-        if (!enableTaskbarNoRecreate() && !FLAG_HIDE_NAVBAR_WINDOW) {
+        if (!enableTaskbarNoRecreate() && !ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
             mWindowManager.removeViewImmediate(mDragLayer);
             mAddedWindow = false;
         }
@@ -860,7 +860,7 @@
     public int getDefaultTaskbarWindowHeight() {
         Resources resources = getResources();
 
-        if (FLAG_HIDE_NAVBAR_WINDOW && mDeviceProfile.isPhone) {
+        if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mDeviceProfile.isPhone) {
             return isThreeButtonNav() ?
                     resources.getDimensionPixelSize(R.dimen.taskbar_size) :
                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_size);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index d13b53f..3f9b66a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -15,22 +15,29 @@
  */
 package com.android.launcher3.taskbar
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
 import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.Rect
 import android.graphics.drawable.GradientDrawable
 import android.util.AttributeSet
+import android.util.Property
 import android.view.Gravity
 import android.view.MotionEvent
 import android.view.View
 import android.widget.LinearLayout
 import android.widget.Switch
 import androidx.core.view.postDelayed
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.launcher3.R
 import com.android.launcher3.popup.ArrowPopup
 import com.android.launcher3.popup.RoundedArrowDrawable
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
 
 /** Popup view with arrow for taskbar pinning */
 class TaskbarDividerPopupView<T : TaskbarActivityContext>
@@ -42,7 +49,8 @@
 ) : ArrowPopup<T>(context, attrs, defStyleAttr) {
     companion object {
         private const val TAG = "TaskbarDividerPopupView"
-        private const val DIVIDER_POPUP_CLOSING_DELAY = 500L
+        private const val DIVIDER_POPUP_CLOSING_DELAY = 333L
+        private const val DIVIDER_POPUP_CLOSING_ANIMATION_DURATION = 83L
 
         @JvmStatic
         fun createAndPopulate(
@@ -63,7 +71,7 @@
     private lateinit var dividerView: View
 
     private val menuWidth =
-        context.resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
+        resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
     private val popupCornerRadius = Themes.getDialogCornerRadius(context)
     private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
     private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
@@ -71,6 +79,8 @@
 
     private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context)
     private var didPreferenceChange = false
+    private var verticalOffsetForPopupView =
+        resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_vertical_margin)
 
     /** Callback invoked when the pinning popup view is closing. */
     var onCloseCallback: (preferenceChanged: Boolean) -> Unit = {}
@@ -94,11 +104,22 @@
         super.onFinishInflate()
         val taskbarSwitchOption = requireViewById<LinearLayout>(R.id.taskbar_switch_option)
         val alwaysShowTaskbarSwitch = requireViewById<Switch>(R.id.taskbar_pinning_switch)
+        val taskbarVisibilityIcon = requireViewById<View>(R.id.taskbar_pinning_visibility_icon)
         alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
-        taskbarSwitchOption.setOnClickListener {
-            alwaysShowTaskbarSwitch.isClickable = true
-            alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
-            onClickAlwaysShowTaskbarSwitchOption()
+        if (ActivityContext.lookupContext<TaskbarActivityContext>(context).isGestureNav) {
+            taskbarSwitchOption.setOnClickListener {
+                alwaysShowTaskbarSwitch.isClickable = true
+                alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
+                onClickAlwaysShowTaskbarSwitchOption()
+            }
+        } else {
+            alwaysShowTaskbarSwitch.isEnabled = false
+        }
+
+        if (!alwaysShowTaskbarSwitch.isEnabled) {
+            taskbarVisibilityIcon.background.setTint(
+                resources.getColor(android.R.color.system_neutral2_500, context.theme)
+            )
         }
     }
 
@@ -171,10 +192,71 @@
         }
     }
 
-    override fun closeComplete() {
+    override fun getExtraVerticalOffset(): Int {
+        return (mActivityContext.deviceProfile.taskbarHeight -
+            mActivityContext.deviceProfile.taskbarIconSize) / 2 + verticalOffsetForPopupView
+    }
+
+    override fun animateClose() {
+        if (!mIsOpen) {
+            return
+        }
+        if (mOpenCloseAnimator != null) {
+            mOpenCloseAnimator.cancel()
+        }
+        mIsOpen = false
+
+        mOpenCloseAnimator = getCloseAnimator()
+
+        mOpenCloseAnimator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    mOpenCloseAnimator = null
+                    if (mDeferContainerRemoval) {
+                        setVisibility(INVISIBLE)
+                    } else {
+                        closeComplete()
+                    }
+                }
+            }
+        )
         onCloseCallback(didPreferenceChange)
         onCloseCallback = {}
-        super.closeComplete()
+        mOpenCloseAnimator.start()
+    }
+
+    private fun getCloseAnimator(): AnimatorSet {
+        val alphaValues = floatArrayOf(1f, 0f)
+        val translateYValue =
+            if (!alwaysShowTaskbarOn) verticalOffsetForPopupView else -verticalOffsetForPopupView
+        val alpha = getAnimatorOfFloat(this, ALPHA, *alphaValues)
+        val arrowAlpha = getAnimatorOfFloat(mArrow, ALPHA, *alphaValues)
+        val translateY =
+            ObjectAnimator.ofFloat(
+                this,
+                TRANSLATION_Y,
+                *floatArrayOf(this.translationY, this.translationY + translateYValue)
+            )
+        val arrowTranslateY =
+            ObjectAnimator.ofFloat(
+                mArrow,
+                TRANSLATION_Y,
+                *floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue)
+            )
+        val animatorSet = AnimatorSet()
+        animatorSet.playTogether(alpha, arrowAlpha, translateY, arrowTranslateY)
+        return animatorSet
+    }
+
+    private fun getAnimatorOfFloat(
+        view: View,
+        property: Property<View, Float>,
+        vararg values: Float
+    ): Animator {
+        val animator: Animator = ObjectAnimator.ofFloat(view, property, *values)
+        animator.setDuration(DIVIDER_POPUP_CLOSING_ANIMATION_DURATION)
+        animator.interpolator = EMPHASIZED_ACCELERATE
+        return animator
     }
 
     private fun onClickAlwaysShowTaskbarSwitchOption() {
@@ -182,7 +264,7 @@
         // Allow switch animation to finish and then close the popup.
         postDelayed(DIVIDER_POPUP_CLOSING_DELAY) {
             if (isOpen) {
-                close(false)
+                close(true)
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 1eb8d53..33ec5a3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -126,7 +126,7 @@
     }
 
     protected void onDestroy() {
-        onDestroy(!TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW);
+        onDestroy(!TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 881f5c4..f1bf4b1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -41,7 +41,7 @@
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
 import com.android.launcher3.anim.AlphaUpdateListener
-import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION
+import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.util.DisplayController
 import java.io.PrintWriter
@@ -99,7 +99,7 @@
             }
 
         windowLayoutParams.providedInsets =
-            if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+            if (enableTaskbarNoRecreate()) {
                 getProvidedInsets(controllers.sharedState!!.insetsFrameProviders!!,
                         insetsRoundedCornerFlag)
             } else {
@@ -225,7 +225,6 @@
             provider.insetsSize = Insets.of(0, 0, rightIndexInset, 0)
         }
 
-
         // When in gesture nav, report the stashed height to the IME, to allow hiding the
         // IME navigation bar.
         val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
@@ -236,6 +235,12 @@
         val imeInsetsSizeOverride =
                 arrayOf(
                         InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+                        InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
+                                // No-op override to keep the size and types in sync with the
+                                // override below (insetsSizeOverrides must have the same length and
+                                // types after the window is added according to
+                                // WindowManagerService#relayoutWindow)
+                                provider.insetsSize)
                 )
         // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
         val visInsetsSizeForTappableElement =
@@ -244,12 +249,11 @@
         val insetsSizeOverrideForTappableElement =
                 arrayOf(
                         InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
-                        InsetsFrameProvider.InsetsSizeOverride(
-                                TYPE_VOICE_INTERACTION,
+                        InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
                                 visInsetsSizeForTappableElement
                         ),
                 )
-        if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        if ((context.isGestureNav || TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
                 && provider.type == tappableElement()) {
             provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
         } else if (provider.type != systemGestures()) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 9bb7e67..ac9c4c7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -41,7 +41,6 @@
 import android.hardware.display.DisplayManager;
 import android.net.Uri;
 import android.os.Handler;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.provider.Settings;
 import android.util.Log;
@@ -71,6 +70,7 @@
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
+import com.android.wm.shell.Flags;
 
 import java.io.PrintWriter;
 import java.util.StringJoiner;
@@ -98,8 +98,8 @@
             | 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);
+    public static final boolean ENABLE_TASKBAR_NAVBAR_UNIFICATION =
+            Flags.enableTaskbarNavbarUnification();
 
     private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
             Settings.Secure.USER_SETUP_COMPLETE);
@@ -198,7 +198,8 @@
         Display display =
                 service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
         mContext = service.createWindowContext(display,
-                FLAG_HIDE_NAVBAR_WINDOW ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL, null);
+                ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
+                null);
         if (enableTaskbarNoRecreate()) {
             mWindowManager = mContext.getSystemService(WindowManager.class);
             mTaskbarRootLayout = new FrameLayout(mContext) {
@@ -250,7 +251,7 @@
                         destroyExistingTaskbar();
                     } else {
                         if (dp != null && isTaskbarPresent(dp)) {
-                            if (FLAG_HIDE_NAVBAR_WINDOW) {
+                            if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
                                 // Re-initialize for screen size change? Should this be done
                                 // by looking at screen-size change flag in configDiff in the
                                 // block above?
@@ -295,7 +296,7 @@
         debugWhyTaskbarNotDestroyed("destroyExistingTaskbar: " + mTaskbarActivityContext);
         if (mTaskbarActivityContext != null) {
             mTaskbarActivityContext.onDestroy();
-            if (!FLAG_HIDE_NAVBAR_WINDOW || enableTaskbarNoRecreate()) {
+            if (!ENABLE_TASKBAR_NAVBAR_UNIFICATION || enableTaskbarNoRecreate()) {
                 mTaskbarActivityContext = null;
             }
         }
@@ -427,7 +428,7 @@
             boolean isTaskbarEnabled = dp != null && isTaskbarPresent(dp);
             debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
                 + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
-                + " FLAG_HIDE_NAVBAR_WINDOW=" + FLAG_HIDE_NAVBAR_WINDOW
+                + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
                 + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
             if (!isTaskbarEnabled) {
                 SystemUiProxy.INSTANCE.get(mContext)
@@ -493,7 +494,7 @@
      *                      and we are using a single window for taskbar and navbar.
      */
     public static boolean isPhoneMode(DeviceProfile deviceProfile) {
-        return TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW && deviceProfile.isPhone;
+        return TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION && deviceProfile.isPhone;
     }
 
     /**
@@ -505,7 +506,7 @@
     }
 
     private boolean isTaskbarPresent(DeviceProfile deviceProfile) {
-        return FLAG_HIDE_NAVBAR_WINDOW || deviceProfile.isTaskbarPresent;
+        return ENABLE_TASKBAR_NAVBAR_UNIFICATION || deviceProfile.isTaskbarPresent;
     }
 
     public void onRotationProposal(int rotation, boolean isValid) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index d1bed3e..cbfa024 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -82,9 +82,12 @@
             taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
         )
 
+        controllers.taskbarOverlayController.hideWindow()
+
         animatorSet.doOnEnd { recreateTaskbarAndUpdatePinningValue() }
         animatorSet.duration = PINNING_ANIMATION_DURATION
         updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(true)
+        taskbarViewController.animateAwayNotificationDotsDuringTaskbarPinningAnimation()
         animatorSet.start()
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 0d06088..0073dc8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -52,6 +52,7 @@
 import androidx.core.view.OneShotPreDrawListener;
 
 import com.android.app.animation.Interpolators;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
@@ -213,7 +214,7 @@
 
         mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
 
-        if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
+        if (TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
             // This gets modified in NavbarButtonsViewController, but the initial value it reads
             // may be incorrect since it's state gets destroyed on taskbar recreate, so reset here
             mTaskbarIconAlpha.get(ALPHA_INDEX_SMALL_SCREEN)
@@ -338,6 +339,17 @@
         }
     }
 
+    /**
+     * Animate away taskbar icon notification dots during the taskbar pinning animation.
+     */
+    public void animateAwayNotificationDotsDuringTaskbarPinningAnimation() {
+        for (View iconView : mTaskbarView.getIconViews()) {
+            if (iconView instanceof BubbleTextView && ((BubbleTextView) iconView).hasDot()) {
+                ((BubbleTextView) iconView).animateDotScale(0);
+            }
+        }
+    }
+
     private void updateTaskbarIconTranslationXForPinning() {
         View[] iconViews = mTaskbarView.getIconViews();
         float scale = mTaskbarIconTranslationXForPinning.value;
@@ -581,6 +593,11 @@
      *                       1 => fully aligned
      */
     public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
+        if (isPhoneMode(launcherDp)) {
+            mIconAlignControllerLazy = null;
+            return;
+        }
+
         boolean isHotseatIconOnTopWhenAligned =
                 mControllers.uiController.isHotseatIconOnTopWhenAligned();
         boolean isStashed = mControllers.taskbarStashController.isStashed();
@@ -609,11 +626,6 @@
      */
     private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
         PendingAnimation setter = new PendingAnimation(100);
-        if (TaskbarManager.isPhoneMode(launcherDp)) {
-            // No animation for icons in small-screen
-            return setter.createPlaybackController();
-        }
-
         mOnControllerPreCreateCallback.run();
         DeviceProfile taskbarDp = mActivity.getDeviceProfile();
         Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
@@ -714,13 +726,19 @@
                 continue;
             }
 
-            int positionInHotseat;
-            if (isAllAppsButton || isTaskbarDividerView) {
-                // Note that there is no All Apps button or taskbar divider view in the hotseat,
+            float positionInHotseat;
+            if (isAllAppsButton) {
+                // Note that there is no All Apps button in the hotseat,
                 // this position is only used as its convenient for animation purposes.
                 positionInHotseat = Utilities.isRtl(child.getResources())
                         ? taskbarDp.numShownHotseatIcons
                         : -1;
+            }  else if (isTaskbarDividerView) {
+                // Note that there is no taskbar divider view in the hotseat,
+                // this position is only used as its convenient for animation purposes.
+                positionInHotseat = Utilities.isRtl(child.getResources())
+                        ? taskbarDp.numShownHotseatIcons - 0.5f
+                        : -0.5f;
             } else if (child.getTag() instanceof ItemInfo) {
                 positionInHotseat = ((ItemInfo) child.getTag()).screenId;
             } else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 12cb8c5..6549ad6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -163,6 +163,7 @@
         mBubble = overflow;
         mBubbleIcon.setImageBitmap(bitmap);
         mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
+        setContentDescription(getResources().getString(R.string.bubble_bar_overflow_description));
     }
 
     /** Returns the bubble being rendered in this view. */
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 22ab79d..165ed80 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -20,6 +20,8 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
 import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
 import static com.android.launcher3.LauncherSettings.Animation.VIEW_BACKGROUND;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
index 481e200..630ef39 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DebugFlag.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.uioverrides.flags;
 
+import static com.android.launcher3.uioverrides.flags.FlagsFactory.TEAMFOOD_FLAG;
+
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.config.FeatureFlags.BooleanFlag;
@@ -35,6 +37,21 @@
         this.description = description;
     }
 
+    /**
+     * Returns {@code true} if this flag's value has been modified from its default.
+     * <p>
+     * This helps to identify which flags have been toggled in log dumps and bug reports to
+     * further help triaging and debugging.
+     */
+    boolean currentValueModified() {
+        switch (defaultValue) {
+            case ENABLED: return !get();
+            case TEAMFOOD: return TEAMFOOD_FLAG.get() != get();
+            case DISABLED: return get();
+            default: return true;
+        }
+    }
+
     @Override
     public String toString() {
         return key + ": defaultValue=" + defaultValue + ", mCurrentValue=" + get();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
index 035beb4..915f4ae 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeviceFlag.java
@@ -29,6 +29,11 @@
     }
 
     @Override
+    boolean currentValueModified() {
+        return super.currentValueModified() || mDefaultValueInCode != get();
+    }
+
+    @Override
     public String toString() {
         return super.toString() + ", mDefaultValueInCode=" + mDefaultValueInCode;
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 6279f63..48d313e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -57,6 +57,7 @@
     public static final String NAMESPACE_LAUNCHER = "launcher";
 
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
+    private static final List<IntFlag> sIntFlags = new ArrayList<>();
     private static SharedPreferences sSharedPreferences;
 
     static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
@@ -132,7 +133,14 @@
     public static IntFlag getIntFlag(
             int bugId, String key, int defaultValueInCode, String description) {
         INSTANCE.mKeySet.add(key);
-        return new IntFlag(DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode));
+        int defaultValue = DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode);
+        if (IS_DEBUG_DEVICE) {
+            IntDeviceFlag flag = new IntDeviceFlag(key, defaultValue, defaultValueInCode);
+            sIntFlags.add(flag);
+            return flag;
+        } else {
+            return new IntFlag(defaultValue);
+        }
     }
 
     static List<DebugFlag> getDebugFlags() {
@@ -163,18 +171,25 @@
             return;
         }
         pw.println("DeviceFlags:");
+        pw.println("  BooleanFlags:");
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
                 if (flag instanceof DeviceFlag) {
-                    pw.println("  " + flag);
+                    pw.println((flag.currentValueModified() ? "  ->" : "    ") + flag);
                 }
             }
         }
-        pw.println("DebugFlags:");
+        pw.println("  IntFlags:");
+        synchronized (sIntFlags) {
+            for (IntFlag flag : sIntFlags) {
+                pw.println("    " + flag);
+            }
+        }
+        pw.println("  DebugFlags:");
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
                 if (!(flag instanceof DeviceFlag)) {
-                    pw.println("  " + flag);
+                    pw.println((flag.currentValueModified() ? "  ->" : "    ") + flag);
                 }
             }
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java
new file mode 100644
index 0000000..4f3b0ae
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java
@@ -0,0 +1,34 @@
+/*
+ * 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.uioverrides.flags;
+
+import com.android.launcher3.config.FeatureFlags.IntFlag;
+
+public class IntDeviceFlag extends IntFlag {
+    public final String key;
+    private final int mDefaultValueInCode;
+
+    public IntDeviceFlag(String key, int currentValue, int defaultValueInCode) {
+        super(currentValue);
+        this.key = key;
+        mDefaultValueInCode = defaultValueInCode;
+    }
+
+    @Override
+    public String toString() {
+        return key + ": mCurrentValue=" + get() + ", defaultValueInCode=" + mDefaultValueInCode;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index b06a978..9ee9d85 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -20,6 +20,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.widget.Toast.LENGTH_SHORT;
+
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
 import static com.android.app.animation.Interpolators.DECELERATE;
 import static com.android.app.animation.Interpolators.OVERSHOOT_1_2;
@@ -351,7 +352,7 @@
                         return ROTATION_0;
                     }
                     return mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation();
-                }, inputConsumer, /* callback = */ () -> {
+                }, inputConsumer, /* onTouchDownCallback = */ () -> {
                     endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
                     endLauncherTransitionController();
                 }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
@@ -1943,7 +1944,7 @@
         mCurrentShift.cancelAnimation();
 
         // Cleanup when switching handlers
-        mInputConsumerProxy.unregisterCallback();
+        mInputConsumerProxy.unregisterOnTouchDownCallback();
         mActivityInitListener.unregister();
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
                 mActivityRestartListener);
@@ -1955,7 +1956,7 @@
             mInputConsumerProxy.destroy();
             mTaskAnimationManager.setLiveTileCleanUpHandler(null);
         }
-        mInputConsumerProxy.unregisterCallback();
+        mInputConsumerProxy.unregisterOnTouchDownCallback();
         endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index c5a88bc..57b9a39 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -66,6 +66,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
 import com.android.quickstep.util.TransformParams;
@@ -151,19 +152,20 @@
             return new FallbackPipToHomeAnimationFactory();
         }
         mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
-        startHomeIntent(mActiveAnimationFactory, runningTaskTarget);
+        startHomeIntent(mActiveAnimationFactory, runningTaskTarget, "FallbackSwipeHandler-home");
         return mActiveAnimationFactory;
     }
 
     private void startHomeIntent(
             @Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory,
-            @Nullable RemoteAnimationTarget runningTaskTarget) {
+            @Nullable RemoteAnimationTarget runningTaskTarget,
+            @NonNull String reason) {
         ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
         Intent intent = new Intent(mGestureState.getHomeIntent());
         if (gestureContractAnimationFactory != null && runningTaskTarget != null) {
             gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
         }
-        startHomeIntentSafely(mContext, intent, options.toBundle());
+        startHomeIntentSafely(mContext, intent, options.toBundle(), reason);
     }
 
     @Override
@@ -185,8 +187,8 @@
             // the PiP task appearing.
             recentsCallback = () -> {
                 callback.run();
-                startHomeIntent(
-                        null /* gestureContractAnimationFactory */, null /* runningTaskTarget */);
+                startHomeIntent(null /* gestureContractAnimationFactory */,
+                        null /* runningTaskTarget */, "FallbackSwipeHandler-resumeLauncher");
             };
         } else {
             recentsCallback = callback;
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 49ec85e..575be5f 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -29,13 +29,15 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
 
-import android.annotation.Nullable;
 import android.annotation.TargetApi;
 import android.content.Intent;
 import android.os.Build;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
@@ -303,6 +305,7 @@
     /**
      * @return the running task for this gesture.
      */
+    @Nullable
     public CachedTaskInfo getRunningTask() {
         return mRunningTask;
     }
@@ -336,7 +339,7 @@
     /**
      * Updates the running task for the gesture to be the given {@param runningTask}.
      */
-    public void updateRunningTask(CachedTaskInfo runningTask) {
+    public void updateRunningTask(@NonNull CachedTaskInfo runningTask) {
         mRunningTask = runningTask;
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 5784c37..feaa063 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -276,8 +276,11 @@
     @Override
     public boolean isInLiveTileMode() {
         Launcher launcher = getCreatedActivity();
-        return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
-                launcher.isStarted();
+
+        return launcher != null
+                && launcher.getStateManager().getState() == OVERVIEW
+                && launcher.isStarted()
+                && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 833bf5f..31fe791 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -220,6 +221,7 @@
                 return true;
             }
             if (cmd.type == TYPE_HOME) {
+                ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(TYPE_HOME)");
                 mService.startActivity(mOverviewComponentObserver.getHomeIntent());
                 return true;
             }
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 60713cf..0a02e99 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -39,6 +39,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
 import java.io.PrintWriter;
@@ -276,20 +277,24 @@
     /**
      * Starts the intent for the current home activity.
      */
-    public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
+    public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options,
+            @NonNull String reason) {
         RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context);
         OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState);
         Intent intent = observer.getHomeIntent();
         observer.onDestroy();
         deviceState.destroy();
-        startHomeIntentSafely(context, intent, options);
+        startHomeIntentSafely(context, intent, options, reason);
     }
 
     /**
      * Starts the intent for the current home activity.
      */
     public static void startHomeIntentSafely(
-            @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options) {
+            @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options,
+            @NonNull String reason) {
+        ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                "OverviewComponentObserver.startHomeIntent: ").append(reason));
         try {
             context.startActivity(homeIntent, options);
         } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 961d8c0..39edd70 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -94,6 +94,7 @@
  * See {@link com.android.quickstep.views.RecentsView}.
  */
 public final class RecentsActivity extends StatefulActivity<RecentsState> {
+    private static final String TAG = "RecentsActivity";
 
     public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER =
             new ActivityTracker<>();
@@ -427,7 +428,7 @@
                 new RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
                 new RemoteTransition(runner.toRemoteTransition(), getIApplicationThread(),
                         "StartHomeFromRecents"));
-        startHomeIntentSafely(this, options.toBundle());
+        startHomeIntentSafely(this, options.toBundle(), TAG);
     }
 
     private final RemoteAnimationFactory mAnimationToHomeFactory =
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 8d26fd4..db5ad82 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -23,9 +23,9 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
-import static com.android.launcher3.util.NavigationMode.TWO_BUTTONS;
 import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
 import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -55,8 +55,11 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.util.Log;
+import android.view.ISystemGestureExclusionListener;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.view.WindowManagerGlobal;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
@@ -74,7 +77,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
-import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
@@ -86,6 +88,8 @@
  */
 public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
 
+    private static final String TAG = "RecentsAnimationDeviceState";
+
     static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
     // TODO: Move to quickstep contract
@@ -95,6 +99,23 @@
     private final Context mContext;
     private final DisplayController mDisplayController;
     private final int mDisplayId;
+
+    private final ISystemGestureExclusionListener mGestureExclusionListener =
+            new ISystemGestureExclusionListener.Stub() {
+                @BinderThread
+                @Override
+                public void onSystemGestureExclusionChanged(int displayId,
+                        Region systemGestureExclusionRegion, Region unrestrictedOrNull) {
+                    if (displayId != mDisplayId) {
+                        return;
+                    }
+                    // Assignments are atomic, it should be safe on binder thread. Also we don't
+                    // think systemGestureExclusionRegion can be null but just in case, don't let
+                    // mExclusionRegion be null.
+                    mExclusionRegion = systemGestureExclusionRegion != null
+                            ? systemGestureExclusionRegion : new Region();
+                }
+            };
     private final RotationTouchHelper mRotationTouchHelper;
     private final TaskStackChangeListener mPipListener;
     // Cache for better performance since it doesn't change at runtime.
@@ -118,7 +139,7 @@
 
     private int mGestureBlockingTaskId = -1;
     private @NonNull Region mExclusionRegion = new Region();
-    private SystemGestureExclusionListenerCompat mExclusionListener;
+    private boolean mExclusionListenerRegistered;
 
     public RecentsAnimationDeviceState(Context context) {
         this(context, false);
@@ -142,19 +163,7 @@
         }
 
         // Register for exclusion updates
-        mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
-            @Override
-            @BinderThread
-            public void onExclusionChanged(Region region) {
-                if (region == null) {
-                    // Don't think this is possible but just in case, don't let it be null.
-                    region = new Region();
-                }
-                // Assignments are atomic, it should be safe on binder thread
-                mExclusionRegion = region;
-            }
-        };
-        runOnDestroy(mExclusionListener::unregister);
+        runOnDestroy(() -> unregisterExclusionListener());
 
         // Register for display changes changes
         mDisplayController.addChangeListener(this);
@@ -247,13 +256,54 @@
             mNavBarPosition = new NavBarPosition(mMode, info);
 
             if (mMode == NO_BUTTON) {
-                mExclusionListener.register();
+                registerExclusionListener();
             } else {
-                mExclusionListener.unregister();
+                unregisterExclusionListener();
             }
         }
     }
 
+    /**
+     * Registers {@link mGestureExclusionListener} for getting exclusion rect changes. Note that we
+     * make binder call on {@link UI_HELPER_EXECUTOR} to avoid jank.
+     */
+    public void registerExclusionListener() {
+        UI_HELPER_EXECUTOR.execute(() -> {
+            if (mExclusionListenerRegistered) {
+                return;
+            }
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .registerSystemGestureExclusionListener(
+                                mGestureExclusionListener, mDisplayId);
+                mExclusionListenerRegistered = true;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to register window manager callbacks", e);
+            }
+        });
+    }
+
+    /**
+     * Unregisters {@link mGestureExclusionListener} if previously registered. We make binder call
+     * on same {@link UI_HELPER_EXECUTOR} as in {@link #registerExclusionListener()} so that
+     * read/write {@link mExclusionListenerRegistered} field is thread safe.
+     */
+    public void unregisterExclusionListener() {
+        UI_HELPER_EXECUTOR.execute(() -> {
+            if (!mExclusionListenerRegistered) {
+                return;
+            }
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .unregisterSystemGestureExclusionListener(
+                                mGestureExclusionListener, mDisplayId);
+                mExclusionListenerRegistered = false;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to unregister window manager callbacks", e);
+            }
+        });
+    }
+
     public void onOneHandedModeChanged(int newGesturalHeight) {
         mRotationTouchHelper.setGesturalHeight(newGesturalHeight);
     }
@@ -280,13 +330,6 @@
     }
 
     /**
-     * @return whether the current nav mode is 2-button-based.
-     */
-    public boolean isTwoButtonNavMode() {
-        return mMode == TWO_BUTTONS;
-    }
-
-    /**
      * @return whether the current nav mode is button-based.
      */
     public boolean isButtonNavMode() {
@@ -391,13 +434,6 @@
     }
 
     /**
-     * @return whether notification panel is expanded
-     */
-    public boolean isNotificationPanelExpanded() {
-        return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0;
-    }
-
-    /**
      * @return whether the global actions dialog is showing
      */
     public boolean isSystemUiDialogShowing() {
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 038a022..92aa9fa 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -17,6 +17,7 @@
 
 import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
 
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -97,6 +98,7 @@
 import com.android.wm.shell.transition.IShellTransitions;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
@@ -398,6 +400,17 @@
     }
 
     @Override
+    public void animateNavBarLongPress(boolean isTouchDown, long durationMs) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.animateNavBarLongPress(isTouchDown, durationMs);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call animateNavBarLongPress", e);
+            }
+        }
+    }
+
+    @Override
     public void notifyAccessibilityButtonClicked(int displayId) {
         if (mSystemUiProxy != null) {
             try {
@@ -1209,7 +1222,7 @@
         }
         try {
             mBackAnimation.setBackToLauncherCallback(callback, runner);
-        } catch (RemoteException e) {
+        } catch (RemoteException | SecurityException e) {
             Log.e(TAG, "Failed call setBackToLauncherCallback", e);
         }
     }
@@ -1249,19 +1262,21 @@
     }
 
     public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
-        if (mRecentTasks != null) {
-            try {
-                final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,
-                        RECENT_IGNORE_UNAVAILABLE, userId);
-                if (rawTasks == null) {
-                    return new ArrayList<>();
-                }
-                return new ArrayList<>(Arrays.asList(rawTasks));
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getRecentTasks", e);
-            }
+        if (mRecentTasks == null) {
+            Log.w(TAG, "getRecentTasks() failed due to null mRecentTasks");
+            return new ArrayList<>();
         }
-        return new ArrayList<>();
+        try {
+            final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,
+                    RECENT_IGNORE_UNAVAILABLE, userId);
+            if (rawTasks == null) {
+                return new ArrayList<>();
+            }
+            return new ArrayList<>(Arrays.asList(rawTasks));
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed call getRecentTasks", e);
+            return new ArrayList<>();
+        }
     }
 
     /**
@@ -1405,7 +1420,7 @@
     public boolean startRecentsActivity(Intent intent, ActivityOptions options,
             RecentsAnimationListener listener) {
         if (mRecentTasks == null) {
-            ActiveGestureLog.INSTANCE.trackEvent(RECENT_TASKS_MISSING);
+            ActiveGestureLog.INSTANCE.addLog("Null mRecentTasks", RECENT_TASKS_MISSING);
             return false;
         }
         final IRecentsAnimationRunner runner = new IRecentsAnimationRunner.Stub() {
@@ -1463,4 +1478,35 @@
             return false;
         }
     }
+
+    public void dump(PrintWriter pw) {
+        pw.println(TAG + ":");
+
+        pw.println("\tmSystemUiProxy=" + mSystemUiProxy);
+        pw.println("\tmPip=" + mPip);
+        pw.println("\tmPipAnimationListener=" + mPipAnimationListener);
+        pw.println("\tmBubbles=" + mBubbles);
+        pw.println("\tmBubblesListener=" + mBubblesListener);
+        pw.println("\tmSplitScreen=" + mSplitScreen);
+        pw.println("\tmSplitScreenListener=" + mSplitScreenListener);
+        pw.println("\tmSplitSelectListener=" + mSplitSelectListener);
+        pw.println("\tmOneHanded=" + mOneHanded);
+        pw.println("\tmShellTransitions=" + mShellTransitions);
+        pw.println("\tmHomeTransitionListener=" + mHomeTransitionListener);
+        pw.println("\tmStartingWindow=" + mStartingWindow);
+        pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
+        pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
+        pw.println("\tmLauncherActivityClass=" + mLauncherActivityClass);
+        pw.println("\tmLauncherUnlockAnimationController=" + mLauncherUnlockAnimationController);
+        pw.println("\tmRecentTasks=" + mRecentTasks);
+        pw.println("\tmRecentTasksListener=" + mRecentTasksListener);
+        pw.println("\tmBackAnimation=" + mBackAnimation);
+        pw.println("\tmBackToLauncherCallback=" + mBackToLauncherCallback);
+        pw.println("\tmBackToLauncherRunner=" + mBackToLauncherRunner);
+        pw.println("\tmDesktopMode=" + mDesktopMode);
+        pw.println("\tmDesktopTaskListener=" + mDesktopTaskListener);
+        pw.println("\tmUnfoldAnimation=" + mUnfoldAnimation);
+        pw.println("\tmUnfoldAnimationListener=" + mUnfoldAnimationListener);
+        pw.println("\tmDragAndDrop=" + mDragAndDrop);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index f0f8aec..20a751b 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.Flags.enableOverviewIconMenu;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 
 import android.annotation.Nullable;
@@ -120,6 +121,7 @@
             public void handleResult(TaskCacheEntry result) {
                 task.icon = result.icon;
                 task.titleDescription = result.contentDescription;
+                task.title = result.title;
                 callback.accept(task);
                 dispatchIconUpdate(task.key.id);
             }
@@ -190,6 +192,10 @@
         if (activityInfo != null) {
             entry.contentDescription = getBadgedContentDescription(
                     activityInfo, task.key.userId, task.taskDescription);
+            if (enableOverviewIconMenu()) {
+                entry.title = Utilities.trim(
+                        activityInfo.applicationInfo.loadLabel(mContext.getPackageManager()));
+            }
         }
 
         mIconCache.put(task.key, entry);
@@ -280,6 +286,7 @@
     private static class TaskCacheEntry {
         public Drawable icon;
         public String contentDescription = "";
+        public String title = "";
     }
 
     void registerTaskVisualsChangeListener(TaskVisualsChangeListener newListener) {
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 662a895..ddddc89 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -197,7 +197,11 @@
                 remoteTargetHandles = gluer.assignTargets(targets);
             }
         }
+        final int recentsActivityRotation =
+                recentsView.getPagedViewOrientedState().getRecentsActivityRotation();
         for (RemoteTargetHandle remoteTargetGluer : remoteTargetHandles) {
+            remoteTargetGluer.getTaskViewSimulator().getOrientationState().setRecentsRotation(
+                    recentsActivityRotation);
             remoteTargetGluer.getTransformParams().setSyncTransactionApplier(applier);
         }
 
@@ -693,15 +697,17 @@
             launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE);
             launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
 
-            windowAnimEndListener = new AnimatorListenerAdapter() {
+            windowAnimEndListener = new AnimationSuccessListener() {
                 @Override
                 public void onAnimationStart(Animator animation) {
                     recentsView.onTaskLaunchedInLiveTileMode();
                 }
 
                 // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+                // This should only be run onAnimationSuccess, otherwise finishRecentsAnimation will
+                // interfere with a rapid swipe up to home in the live tile + running task case.
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void onAnimationSuccess(Animator animation) {
                     recentsView.finishRecentsAnimation(false /* toRecents */, () -> {
                         recentsView.post(() -> {
                             stateManager.moveToRestState();
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index f1af2ed..a2a6dde 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -29,6 +29,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
@@ -178,6 +179,7 @@
     /**
      * Returns the CachedTaskInfo for the top most task
      */
+    @NonNull
     @UiThread
     public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
         if (filterOnlyVisibleRecents) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 98a9938..0ad56fb 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -854,7 +854,11 @@
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             gestureState = new GestureState(mOverviewComponentObserver,
                     ActiveGestureLog.INSTANCE.getLogId());
-            taskInfo = previousGestureState.getRunningTask();
+            TopTaskTracker.CachedTaskInfo previousTaskInfo = previousGestureState.getRunningTask();
+            // previousTaskInfo can be null iff previousGestureState == GestureState.DEFAULT_STATE
+            taskInfo = previousTaskInfo != null
+                    ? previousTaskInfo
+                    : TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
             gestureState.updateRunningTask(taskInfo);
             gestureState.updateLastStartedTaskIds(previousGestureState.getLastStartedTaskIds());
             gestureState.updatePreviouslyAppearedTaskIds(
@@ -869,7 +873,7 @@
 
         // Log initial state for the gesture.
         ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=")
-                .append(taskInfo == null ? "no running task" : taskInfo.getPackageName()));
+                .append(taskInfo.getPackageName()));
         ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current SystemUi state flags=")
                 .append(mDeviceState.getSystemUiStateString()));
         return gestureState;
@@ -1078,21 +1082,22 @@
 
         reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded");
 
+        TopTaskTracker.CachedTaskInfo runningTask = gestureState.getRunningTask();
         // Use overview input consumer for sharesheets on top of home.
         boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
-                && gestureState.getRunningTask() != null
-                && gestureState.getRunningTask().isRootChooseActivity();
+                && runningTask != null
+                && runningTask.isRootChooseActivity();
 
         // In the case where we are in an excluded, translucent overlay, ignore it and treat the
         // running activity as the task behind the overlay.
-        TopTaskTracker.CachedTaskInfo otherVisibleTask = gestureState.getRunningTask() == null
+        TopTaskTracker.CachedTaskInfo otherVisibleTask = runningTask == null
                 ? null
-                : gestureState.getRunningTask().otherVisibleTaskThisIsExcludedOver();
+                : runningTask.otherVisibleTaskThisIsExcludedOver();
         if (otherVisibleTask != null) {
             ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
                     .append(otherVisibleTask.getPackageName())
                     .append(" because the previous task running on top of this one (")
-                    .append(gestureState.getRunningTask().getPackageName())
+                    .append(runningTask.getPackageName())
                     .append(") was excluded from recents"));
             gestureState.updateRunningTask(otherVisibleTask);
         }
@@ -1112,7 +1117,7 @@
                     forceOverviewInputConsumer,
                     reasonString.append(SUBSTRING_PREFIX)
                             .append("is in live tile mode, trying to use overview input consumer"));
-        } else if (gestureState.getRunningTask() == null) {
+        } else if (runningTask == null) {
             return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
                     .append("running task == null"));
         } else if (previousGestureAnimatedToLauncher
@@ -1130,7 +1135,7 @@
                                             ? "launcher resumed through a shell transition"
                                             : "forceOverviewInputConsumer == true"))
                             .append(", trying to use overview input consumer"));
-        } else if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
+        } else if (mDeviceState.isGestureBlockedTask(runningTask)) {
             return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
                     .append("is gesture-blocked task, trying to use default input consumer"));
         } else {
@@ -1299,6 +1304,7 @@
         Log.i(TAG, "preloadOverview: forSUWAllSet=" + forSUWAllSet
                 + ", isHomeAndOverviewSame=" + mOverviewComponentObserver.isHomeAndOverviewSame());
 
+        ActiveGestureLog.INSTANCE.addLog("preloadRecentsAnimation");
         mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
     }
 
@@ -1373,6 +1379,7 @@
         mTaskbarManager.dumpLogs("", pw);
         pw.println("AssistStateManager:");
         AssistStateManager.INSTANCE.get(this).dump("  ", pw);
+        SystemUiProxy.INSTANCE.get(this).dump(pw);
     }
 
     private AbsSwipeUpHandler createLauncherSwipeHandler(
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 2a35584..f264364 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -68,6 +68,7 @@
  */
 public class DeviceLockedInputConsumer implements InputConsumer,
         RecentsAnimationCallbacks.RecentsAnimationListener, BuilderProxy {
+    private final String TAG = "DeviceLockedInputConsumer";
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null;
     private static int getFlagForIndex(int index, String name) {
@@ -207,7 +208,12 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    if (ENABLE_SHELL_TRANSITIONS) {
+                    if (dismissTask) {
+                        // Just start the home intent so the user is prompted to unlock the device.
+                        // This will come back and cancel the interaction.
+                        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
+                        mHomeLaunched = true;
+                    } else if (ENABLE_SHELL_TRANSITIONS) {
                         if (mTaskAnimationManager.getCurrentCallbacks() != null) {
                             if (mRecentsAnimationController != null) {
                                 finishRecentsAnimationForShell(dismissTask);
@@ -217,11 +223,6 @@
                                 mDismissTask = dismissTask;
                             }
                         }
-                    } else if (dismissTask) {
-                        // For now, just start the home intent so user is prompted to
-                        // unlock the device.
-                        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
-                        mHomeLaunched = true;
                     }
                     mStateCallback.setState(STATE_HANDLER_INVALIDATED);
                 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index 32f7bd5..d38376c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -38,7 +38,6 @@
     private final NavHandleLongPressHandler mNavHandleLongPressHandler;
     private final float mNavHandleWidth;
     private final float mScreenWidth;
-    private final ViewConfiguration mViewConfiguration;
 
     private final Runnable mTriggerLongPress = this::triggerLongPress;
     private final float mTouchSlopSquared;
@@ -49,7 +48,6 @@
     public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
             InputMonitorCompat inputMonitor, RecentsAnimationDeviceState deviceState) {
         super(delegate, inputMonitor);
-        mViewConfiguration = ViewConfiguration.get(context);
         mNavHandleWidth = context.getResources().getDimensionPixelSize(
                 R.dimen.navigation_home_handle_width);
         mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
@@ -69,6 +67,18 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
+        if (mDelegate.allowInterceptByParent()) {
+            handleMotionEvent(ev);
+        } else if (MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)) {
+            cancelLongPress();
+        }
+
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
+        }
+    }
+
+    private void handleMotionEvent(MotionEvent ev) {
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN -> {
                 if (mCurrentDownEvent != null) {
@@ -103,10 +113,6 @@
             MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
             MAIN_EXECUTOR.getHandler().post(mTriggerLongPress);
         }
-
-        if (mState != STATE_ACTIVE) {
-            mDelegate.onMotionEvent(ev);
-        }
     }
 
     private void triggerLongPress() {
@@ -114,7 +120,7 @@
         if (longPressRunnable != null) {
             OtherActivityInputConsumer oaic = getInputConsumerOfClass(
                     OtherActivityInputConsumer.class);
-            if (oaic != null) {
+            if (oaic != null && oaic.hasStartedTouchTracking()) {
                 oaic.setForceFinishRecentsTransitionCallback(longPressRunnable);
                 setActive(mCurrentDownEvent);
             } else {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e724547..eedd204 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -413,6 +413,14 @@
     }
 
     /**
+     * Returns whether this input consumer has started touch tracking (if touch tracking is not
+     * deferred).
+     */
+    public boolean hasStartedTouchTracking() {
+        return mInteractionHandler != null;
+    }
+
+    /**
      * Called when the gesture has ended. Does not correlate to the completion of the interaction as
      * the animation can still be running.
      */
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index b70fe8e..41730bb 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -37,6 +37,7 @@
 
 public class OverviewWithoutFocusInputConsumer implements InputConsumer,
         TriggerSwipeUpTouchTracker.OnSwipeUpListener {
+    private static final String TAG = "OverviewWithoutFocusInputConsumer";
 
     private final Context mContext;
     private final InputMonitorCompat mInputMonitor;
@@ -77,7 +78,7 @@
 
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
-        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
+        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
                 ? mGestureState.getEndTarget().containerType
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index c9c64b6..6dcb7bc 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -53,6 +53,7 @@
 public class ProgressDelegateInputConsumer implements InputConsumer,
         RecentsAnimationCallbacks.RecentsAnimationListener,
         SingleAxisSwipeDetector.Listener {
+    private static final String TAG = "ProgressDelegateInputConsumer";
 
     private static final float SWIPE_DISTANCE_THRESHOLD = 0.2f;
 
@@ -165,7 +166,7 @@
             mRecentsAnimationController.finishController(endToRecents /* toRecents */,
                     null /* callback */, false /* sendUserLeaveHint */);
         } else if (endToRecents) {
-            startHomeIntentSafely(mContext, null);
+            startHomeIntentSafely(mContext, null, TAG);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 49814df..fce188f 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -15,6 +15,9 @@
  */
 package com.android.quickstep.interaction;
 
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
 import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.Utilities.mapBoundToRange;
@@ -47,6 +50,8 @@
 import android.util.Log;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
+import android.view.Window;
+import android.view.WindowInsetsController;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.ImageView;
@@ -78,6 +83,7 @@
  * for the gestural system navigation.
  */
 public class AllSetActivity extends Activity {
+    private static final String TAG = "AllSetActivity";
 
     private static final String LOG_TAG = "AllSetActivity";
     private static final String URI_SYSTEM_NAVIGATION_SETTING =
@@ -121,6 +127,17 @@
         Resources resources = getResources();
         int mode = resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
         boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
+
+        int systemBarsMask = APPEARANCE_LIGHT_STATUS_BARS | APPEARANCE_LIGHT_NAVIGATION_BARS;
+        int systemBarsAppearance = isDarkTheme ? 0 : systemBarsMask;
+        Window window = getWindow();
+        WindowInsetsController insetsController = window == null
+                ? null
+                : window.getInsetsController();
+        if (insetsController != null) {
+            insetsController.setSystemBarsAppearance(systemBarsAppearance, systemBarsMask);
+        }
+
         Intent intent = getIntent();
         int accentColor = intent.getIntExtra(
                 isDarkTheme ? EXTRA_ACCENT_COLOR_DARK_MODE : EXTRA_ACCENT_COLOR_LIGHT_MODE,
@@ -299,7 +316,9 @@
         if (mBackgroundAnimatorListener != null) {
             mAnimatedBackground.removeAnimatorListener(mBackgroundAnimatorListener);
         }
-        dispatchLauncherAnimStartEnd();
+        if (!isChangingConfigurations()) {
+            dispatchLauncherAnimStartEnd();
+        }
     }
 
     private AnimatedFloat createSwipeUpProxy(GestureState state) {
@@ -356,7 +375,7 @@
         @Override
         public boolean performAccessibilityAction(View host, int action, Bundle args) {
             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
-                startHomeIntentSafely(AllSetActivity.this, null);
+                startHomeIntentSafely(AllSetActivity.this, null, TAG);
                 finish();
                 return true;
             }
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index d102502..a36f501 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -19,6 +19,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -26,6 +27,7 @@
 import android.view.Display;
 import android.view.View;
 import android.view.Window;
+import android.view.WindowInsets;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -41,6 +43,7 @@
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 import com.android.quickstep.util.TISBindHelper;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /** Shows the gesture interactive sandbox in full screen mode. */
@@ -101,8 +104,11 @@
             correctUserOrientation();
         }
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+
+        initWindowInsets();
     }
 
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -113,6 +119,42 @@
         }
     }
 
+    private void initWindowInsets() {
+        View root = findViewById(android.R.id.content);
+        root.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                updateExclusionRects(root);
+            }
+        });
+
+        // Return CONSUMED if you don't want want the window insets to keep being
+        // passed down to descendant views.
+        root.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+            @Override
+            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+                return WindowInsets.CONSUMED;
+            }
+        });
+    }
+
+    private void updateExclusionRects(View rootView) {
+        Insets gestureInsets = rootView.getRootWindowInsets()
+                .getInsets(WindowInsets.Type.systemGestures());
+        ArrayList<Rect> exclusionRects = new ArrayList<>();
+        // Add rect for left
+        exclusionRects.add(new Rect(0, 0, gestureInsets.left, rootView.getHeight()));
+        // Add rect for right
+        exclusionRects.add(new Rect(
+                rootView.getWidth() - gestureInsets.right,
+                0,
+                rootView.getWidth(),
+                rootView.getHeight()
+        ));
+        rootView.setSystemGestureExclusionRects(exclusionRects);
+    }
+
     /**
      * Gesture animations are only in landscape for large screens and portrait for mobile. This
      * method enforces the following flows:
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index ee72144..4359294 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -147,24 +147,15 @@
                     break;
                 case TASK_APPEARED:
                     errorDetected |= printErrorIfTrue(
-                            !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK),
-                            prefix,
-                            /* errorMessage= */ "onTasksAppeared called "
-                                    + "before/without setting end target to new task",
-                            writer);
-                    errorDetected |= printErrorIfTrue(
                             !encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED),
                             prefix,
                             /* errorMessage= */ "onTasksAppeared was not expected to be called",
                             writer);
-                    break;
-                case EXPECTING_TASK_APPEARED:
-                    errorDetected |= printErrorIfTrue(
-                            !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK),
-                            prefix,
-                            /* errorMessage= */ "expecting onTasksAppeared to be called "
-                                    + "before/without setting end target to new task",
-                            writer);
+                    if (encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED)) {
+                        // Remove both events so that we can properly detect following errors.
+                        encounteredEvents.remove(GestureEvent.EXPECTING_TASK_APPEARED);
+                        encounteredEvents.remove(GestureEvent.TASK_APPEARED);
+                    }
                     break;
                 case LAUNCHER_DESTROYED:
                     errorDetected |= printErrorIfTrue(
@@ -282,6 +273,7 @@
                 case START_RECENTS_ANIMATION:
                     lastStartRecentAnimationEventEntryTime = eventEntry.getTime();
                     break;
+                case EXPECTING_TASK_APPEARED:
                 case MOTION_DOWN:
                 case SET_END_TARGET:
                 case SET_END_TARGET_HOME:
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
index 91b53c7..cb44a1a 100644
--- a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -25,6 +25,8 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
+import androidx.annotation.Nullable;
+
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.SimpleOrientationTouchTransformer;
 import com.android.systemui.shared.system.InputConsumerController;
@@ -42,7 +44,10 @@
     private final Context mContext;
     private final Supplier<Integer> mRotationSupplier;
     private final InputConsumerController mInputConsumerController;
-    private Runnable mCallback;
+
+    /** Called if a new InputConsumer is created via touch down event. */
+    private @Nullable Runnable mOnTouchDownCallback;
+
     private Supplier<InputConsumer> mConsumerSupplier;
 
     // The consumer is created lazily on demand.
@@ -54,11 +59,11 @@
 
     public InputConsumerProxy(Context context, Supplier<Integer> rotationSupplier,
             InputConsumerController inputConsumerController,
-            Runnable callback, Supplier<InputConsumer> consumerSupplier) {
+            Runnable onTouchDownCallback, Supplier<InputConsumer> consumerSupplier) {
         mContext = context;
         mRotationSupplier = rotationSupplier;
         mInputConsumerController = inputConsumerController;
-        mCallback = callback;
+        mOnTouchDownCallback = onTouchDownCallback;
         mConsumerSupplier = consumerSupplier;
     }
 
@@ -82,7 +87,7 @@
                 onInputConsumerMotionEvent(event);
             }
         } else if (ev instanceof KeyEvent) {
-            initInputConsumerIfNeeded();
+            initInputConsumerIfNeeded(/* isFromTouchDown= */ false);
             mInputConsumer.onKeyEvent((KeyEvent) ev);
             return true;
         }
@@ -105,7 +110,7 @@
 
         if (action == ACTION_DOWN) {
             mTouchInProgress = true;
-            initInputConsumerIfNeeded();
+            initInputConsumerIfNeeded(/* isFromTouchDown= */ true);
         } else if (action == ACTION_CANCEL || action == ACTION_UP) {
             // Finish any pending actions
             mTouchInProgress = false;
@@ -123,7 +128,7 @@
     }
 
     private void onInputConsumerHoverEvent(MotionEvent ev) {
-        initInputConsumerIfNeeded();
+        initInputConsumerIfNeeded(/* isFromTouchDown= */ false);
         if (mInputConsumer != null) {
             SimpleOrientationTouchTransformer.INSTANCE.get(mContext).transform(ev,
                     mRotationSupplier.get());
@@ -141,14 +146,15 @@
         mInputConsumerController.setInputListener(null);
     }
 
-    public void unregisterCallback() {
-        mCallback = null;
+    /** Sets mOnToudhCownCallback = null. */
+    public void unregisterOnTouchDownCallback() {
+        mOnTouchDownCallback = null;
     }
 
-    private void initInputConsumerIfNeeded() {
+    private void initInputConsumerIfNeeded(boolean isFromTouchDown) {
         if (mInputConsumer == null) {
-            if (mCallback != null) {
-                mCallback.run();
+            if (isFromTouchDown && mOnTouchDownCallback != null) {
+                mOnTouchDownCallback.run();
             }
             mInputConsumer = mConsumerSupplier.get();
             mConsumerSupplier = null;
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index fce38e1..fe6ce46 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -159,8 +159,8 @@
             // to other classes like PipTaskOrganizer / RecentsAnimationController to complete
             // the cleanup.
             mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
-                    mAppBounds, new IconProvider(context).getIcon(mActivityInfo),
-                    appIconSizePx);
+                    mAppBounds, mDestinationBounds,
+                    new IconProvider(context).getIcon(mActivityInfo), appIconSizePx);
             final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
             mPipContentOverlay.attach(tx, mLeash);
         } else {
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index ca680db..ebcef30 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -16,13 +16,10 @@
 package com.android.quickstep.util;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 
 import android.util.FloatProperty;
 import android.view.RemoteAnimationTarget;
 
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.Utilities;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
 
@@ -152,19 +149,7 @@
                 if (activityType == ACTIVITY_TYPE_HOME) {
                     mHomeBuilderProxy.onBuildTargetParams(builder, app, this);
                 } else {
-                    // Fade out translucent overlay.
-                    // TODO(b/303351074): use app.isNotInRecents directly once it is fixed.
-                    boolean isNotInRecents = app.taskInfo != null
-                            && (app.taskInfo.baseIntent.getFlags()
-                                    & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
-                    if (app.isTranslucent && isNotInRecents) {
-                        float progress = Utilities.boundToRange(getProgress(), 0, 1);
-                        builder.setAlpha(1 - Interpolators.DECELERATE_QUINT
-                                .getInterpolation(progress));
-                    } else {
-                        builder.setAlpha(getTargetAlpha());
-                    }
-
+                    builder.setAlpha(getTargetAlpha());
                     proxy.onBuildTargetParams(builder, app, this);
                 }
             } else {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index a4a53d1..5b1d614 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -27,7 +27,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -170,7 +169,7 @@
                         (task) -> {
                             setIcon(mIconView2, task.icon);
                             if (enableOverviewIconMenu()) {
-                                setText(mIconView2, TaskUtils.getTitle(getContext(), task));
+                                setText(mIconView2, task.title);
                             }
                             mDigitalWellBeingToast2.initialize(mSecondaryTask);
                             mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 5093f22..ab51a70 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -35,6 +35,7 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 import static com.android.quickstep.TaskOverlayFactory.getEnabledShortcuts;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
 import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
@@ -106,6 +107,7 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.BorderAnimator;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
@@ -859,6 +861,7 @@
                     getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
+                ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
                 RecentsView recentsView = getRecentsView();
                 if (recentsView.getRunningTaskViewId() != -1) {
                     recentsView.onTaskLaunchedInLiveTileMode();
@@ -1096,7 +1099,7 @@
                         (task) -> {
                             setIcon(mIconView, task.icon);
                             if (enableOverviewIconMenu()) {
-                                setText(mIconView, TaskUtils.getTitle(getContext(), task));
+                                setText(mIconView, task.title);
                             }
                             mDigitalWellBeingToast.initialize(task);
                         });
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 37fcf43..00756ad 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -59,7 +59,7 @@
 
     @Test
     fun getKidsLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = true
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
             getLayoutter(
@@ -74,7 +74,7 @@
 
     @Test
     fun getSetupLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = true
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
             getLayoutter(
@@ -89,7 +89,7 @@
 
     @Test
     fun getTaskbarNavLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = true
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
             getLayoutter(
@@ -104,7 +104,7 @@
 
     @Test(expected = IllegalStateException::class)
     fun noValidLayoutForLargeScreenTaskbarNotPresent() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         getLayoutter(
             isKidsMode = false,
@@ -117,7 +117,7 @@
 
     @Test
     fun getTaskbarPortraitLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
             getLayoutter(
@@ -132,7 +132,7 @@
 
     @Test
     fun getTaskbarLandscapeLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         setDeviceProfileLandscape()
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
@@ -148,7 +148,7 @@
 
     @Test
     fun getTaskbarSeascapeLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         setDeviceProfileLandscape()
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
@@ -164,7 +164,7 @@
 
     @Test(expected = IllegalStateException::class)
     fun noValidLayoutForPhoneGestureNav() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(TaskbarManager.ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         getLayoutter(
             isKidsMode = false,
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 16235e0..281516c 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -162,7 +162,8 @@
     @Before
     public void setUp() {
         mLauncher.onTestStart();
-        AbstractLauncherUiTest.verifyKeyguardInvisible();
+        AbstractLauncherUiTest.waitForSetupWizardDismissal();
+        AbstractLauncherUiTest.onTestStart();
     }
 
     @After
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
index f6368b0..c4c95bc 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
@@ -16,6 +16,8 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -25,6 +27,7 @@
 
 import com.android.launcher3.tapl.OverviewTaskMenu;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -72,6 +75,7 @@
     }
 
     @Test
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/288939273
     public void testSplitTaskTapBothIconMenus() {
         createAndLaunchASplitPair();
 
@@ -92,7 +96,7 @@
         startTestActivity(2);
         startTestActivity(3);
 
-        if (mLauncher.isTablet()) {
+        if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) {
             mLauncher.goHome().switchToOverview().getOverviewActions()
                     .clickSplit()
                     .getTestActivityTask(2)
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index d45c225..0966350 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
 
@@ -46,6 +48,7 @@
 import com.android.launcher3.ui.TaplTestsLauncher3;
 import com.android.launcher3.util.Wait;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
 import com.android.quickstep.views.RecentsView;
@@ -179,6 +182,8 @@
     @PortraitLandscape
     @PlatinumTest(focusArea = "launcher")
     public void testOverviewActions() throws Exception {
+        assumeFalse("Skipping Overview Actions tests for grid only overview",
+                mLauncher.isTablet() && mLauncher.isGridOnlyOverviewEnabled());
         // Experimenting for b/165029151:
         final Overview overview = mLauncher.goHome().switchToOverview();
         if (overview.hasTasks()) overview.dismissAllTasks();
@@ -334,7 +339,6 @@
     @Test
     @PortraitLandscape
     @NavigationModeSwitch
-    @PlatinumTest(focusArea = "launcher")
     public void testPressBack() throws Exception {
         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 READ_DEVICE_CONFIG_PERMISSION);
@@ -354,7 +358,7 @@
     @PortraitLandscape
     @TaskbarModeSwitch(mode = PERSISTENT)
     @PlatinumTest(focusArea = "launcher")
-    @ScreenRecord
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/309820115
     public void testOverviewForTablet() throws Exception {
         assumeTrue(mLauncher.isTablet());
 
@@ -377,7 +381,9 @@
         // Test opening the task.
         overview.getCurrentTask().open();
         assertTrue("Test activity didn't open from Overview",
-                mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity10")),
+                mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text(
+                                mLauncher.isGridOnlyOverviewEnabled() ? "TestActivity12"
+                                        : "TestActivity13")),
                         DEFAULT_UI_TIMEOUT));
 
         // Scroll the task offscreen as it is now first
@@ -398,16 +404,17 @@
                 (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
                         launcher)) <= 1)));
 
-        // Test dismissing more tasks.
-        assertTrue("Launcher internal state didn't remain in Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        overview.getCurrentTask().dismiss();
-        assertTrue("Launcher internal state didn't remain in Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        overview.getCurrentTask().dismiss();
-        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals",
-                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-                        launcher)) <= 1)));
+        // TODO(b/308841019): Re-enable after fixing Overview jank when dismiss
+//        // Test dismissing more tasks.
+//        assertTrue("Launcher internal state didn't remain in Overview",
+//                isInState(() -> LauncherState.OVERVIEW));
+//        overview.getCurrentTask().dismiss();
+//        assertTrue("Launcher internal state didn't remain in Overview",
+//                isInState(() -> LauncherState.OVERVIEW));
+//        overview.getCurrentTask().dismiss();
+//        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals",
+//                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
+//                        launcher)) <= 1)));
 
         // Test dismissing all tasks.
         mLauncher.goHome().switchToOverview().dismissAllTasks();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index acbb58f..d3bf56f 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -108,7 +108,6 @@
     }
 
     @Test
-    @Ignore("Enable once App Pairs flagged on. These cause memory leaks b/297135374")
     public void testSaveAppPairMenuItemExistsOnSplitPair() throws Exception {
         assumeTrue("App pairs feature is currently not enabled, no test needed",
                 Flags.enableAppPairs());
@@ -124,7 +123,6 @@
     }
 
     @Test
-    @Ignore("Enable once App Pairs flagged on. These cause memory leaks b/297135374")
     public void testSaveAppPairMenuItemDoesNotExistOnSingleTask() throws Exception {
         assumeTrue("App pairs feature is currently not enabled, no test needed",
                 Flags.enableAppPairs());
@@ -143,7 +141,7 @@
         startTestActivity(2);
         startTestActivity(3);
 
-        if (mLauncher.isTablet()) {
+        if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) {
             mLauncher.goHome().switchToOverview().getOverviewActions()
                     .clickSplit()
                     .getTestActivityTask(2)
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index a54dc2d..b365173 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -18,11 +18,15 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.ArrayMap;
+import android.util.DisplayMetrics;
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 
@@ -35,7 +39,6 @@
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.LauncherModelHelper;
 import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.ReflectionHelpers;
 import com.android.launcher3.util.RotationUtils;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
@@ -61,6 +64,7 @@
     public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
         new TaskMatrixVerifier()
                 .withLauncherSize(1200, 2450)
+                .withDensityDpi(420)
                 .withInsets(new Rect(0, 80, 0, 120))
                 .verifyNoTransforms();
     }
@@ -69,6 +73,7 @@
     public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
         new TaskMatrixVerifier()
                 .withLauncherSize(1200, 2450)
+                .withDensityDpi(420)
                 .withInsets(new Rect(55, 80, 55, 120))
                 .verifyNoTransforms();
     }
@@ -77,6 +82,7 @@
     public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
         new TaskMatrixVerifier()
                 .withLauncherSize(2450, 1250)
+                .withDensityDpi(420)
                 .withInsets(new Rect(0, 80, 0, 40))
                 .verifyNoTransforms();
     }
@@ -85,6 +91,7 @@
     public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
         new TaskMatrixVerifier()
                 .withLauncherSize(2450, 1250)
+                .withDensityDpi(420)
                 .withInsets(new Rect(0, 80, 120, 0))
                 .verifyNoTransforms();
     }
@@ -93,6 +100,7 @@
     public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
         new TaskMatrixVerifier()
                 .withLauncherSize(2450, 1250)
+                .withDensityDpi(420)
                 .withInsets(new Rect(55, 80, 55, 120))
                 .verifyNoTransforms();
     }
@@ -101,6 +109,7 @@
     public void taskProperlyScaled_landscape_rotated() {
         new TaskMatrixVerifier()
                 .withLauncherSize(1200, 2450)
+                .withDensityDpi(420)
                 .withInsets(new Rect(0, 80, 0, 120))
                 .withAppBounds(
                         new Rect(0, 0, 2450, 1200),
@@ -112,6 +121,7 @@
     private static class TaskMatrixVerifier extends TransformParams {
 
         private Point mDisplaySize = new Point();
+        private int mDensityDpi = DisplayMetrics.DENSITY_DEFAULT;
         private Rect mDisplayInsets = new Rect();
         private Rect mAppBounds = new Rect();
         private Rect mLauncherInsets = new Rect();
@@ -129,6 +139,11 @@
             return this;
         }
 
+        TaskMatrixVerifier withDensityDpi(int densityDpi) {
+            mDensityDpi = densityDpi;
+            return this;
+        }
+
         TaskMatrixVerifier withInsets(Rect insets) {
             mDisplayInsets.set(insets);
             mLauncherInsets.set(insets);
@@ -173,13 +188,17 @@
                         new ArrayMap<>();
                 perDisplayBoundsCache.put(cdi.normalize(), allBounds);
 
-                DisplayController.Info mockInfo = new Info(
-                        helper.sandboxContext, wmProxy, perDisplayBoundsCache);
+                Configuration configuration = new Configuration();
+                configuration.densityDpi = mDensityDpi;
+                Context configurationContext = helper.sandboxContext.createConfigurationContext(
+                        configuration);
 
-                DisplayController controller =
-                        DisplayController.INSTANCE.get(helper.sandboxContext);
-                controller.close();
-                ReflectionHelpers.setField(controller, "mInfo", mockInfo);
+                DisplayController.Info info = new Info(
+                        configurationContext, wmProxy, perDisplayBoundsCache);
+
+                DisplayController mockController = mock(DisplayController.class);
+                when(mockController.getInfo()).thenReturn(info);
+                helper.sandboxContext.putObject(DisplayController.INSTANCE, mockController);
 
                 mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext)
                         .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation);
@@ -189,7 +208,7 @@
                         FallbackActivityInterface.INSTANCE);
                 tvs.setDp(mDeviceProfile);
 
-                int launcherRotation = mockInfo.rotation;
+                int launcherRotation = info.rotation;
                 if (mAppRotation < 0) {
                     mAppRotation = launcherRotation;
                 }
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 39f1477..138e1b3 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -133,7 +133,7 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ವಿಜೆಟ್ ಪಟ್ಟಿಯನ್ನು ಮುಚ್ಚಲಾಗಿದೆ"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಸೇರಿಸಿ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ಐಟಂ ಇಲ್ಲಿಗೆ ಸರಿಸಿ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ಮುಖಪುಟ ಪರದೆಗೆ ಐಟಂ ಸೇರಿಸಲಾಗಿದೆ"</string>
+    <string name="item_added_to_workspace" msgid="4211073925752213539">"ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಐಟಂ ಸೇರಿಸಲಾಗಿದೆ"</string>
     <string name="item_removed" msgid="851119963877842327">"ಐಟಂ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
     <string name="undo" msgid="4151576204245173321">"ರದ್ದುಮಾಡಿ"</string>
     <string name="action_move" msgid="4339390619886385032">"ಐಟಂ ಸರಿಸಿ"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f046eca..1a960f5 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -268,6 +268,10 @@
         <attr name="maxAvailableSize" format="dimension" />
     </declare-styleable>
 
+    <declare-styleable name="ResponsiveSpecGroup">
+        <attr name="maxAspectRatio" format="float" />
+    </declare-styleable>
+
     <declare-styleable name="WorkspaceSpec">
         <attr name="specType" />
         <attr name="maxAvailableSize" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 81c2337..c0a1e0a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -284,7 +284,8 @@
     <dimen name="pre_drag_view_scale">6dp</dimen>
     <!-- an icon with shortcuts must be dragged this far before the container is removed. -->
     <dimen name="deep_shortcuts_start_drag_threshold">16dp</dimen>
-    <dimen name="deep_shortcut_icon_size">32dp</dimen>
+    <!-- Possibly related to b/235886078, icon needs to be scaled up to match expected visual size of 32 dp -->
+    <dimen name="deep_shortcut_icon_size">35dp</dimen>
     <dimen name="popup_margin">2dp</dimen>
     <dimen name="popup_single_item_radius">100dp</dimen>
     <dimen name="popup_smaller_radius">4dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4e865d0..f08f8f0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -456,4 +456,8 @@
 
     <!-- Private space label -->
     <string name="private_space_label">Private space</string>
+
+    <!-- Strings for bubble bar -->
+    <!-- content description for the overflow bubble [CHAR_LIMIT=none] -->
+    <string name="bubble_bar_overflow_description">Overflow</string>
 </resources>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 189db21..79b831e 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -32,6 +32,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
 
+import com.android.launcher3.LauncherConstants.ActivityCodes;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
@@ -270,7 +271,7 @@
                         .startConfigActivity(
                                 mLauncher,
                                 mWidgetView.getAppWidgetId(),
-                                Launcher.REQUEST_RECONFIGURE_APPWIDGET);
+                                ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET);
             });
             if (!hasSeenReconfigurableWidgetEducationTip()) {
                 post(() -> {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ddcb1e6..e2e528c 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -294,7 +294,7 @@
         }
     }
 
-    private void animateDotScale(float... dotScales) {
+    public void animateDotScale(float... dotScales) {
         cancelDotScaleAnim();
         mDotScaleAnim = ObjectAnimator.ofFloat(this, DOT_SCALE_PROPERTY, dotScales);
         mDotScaleAnim.addListener(new AnimatorListenerAdapter() {
@@ -646,7 +646,7 @@
         return mForceHideDot;
     }
 
-    private boolean hasDot() {
+    public boolean hasDot() {
         return mDotInfo != null;
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 73cd8c4..59f3f0d 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableOverviewIconMenu;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_DEFAULT;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_LANDSCAPE;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
@@ -24,7 +25,6 @@
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.Utilities.pxFromSp;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_MULTI_DISPLAY_PARTIAL_DEPTH;
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
@@ -54,14 +54,12 @@
 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.CalculatedHotseatSpec;
-import com.android.launcher3.responsive.CalculatedWorkspaceSpec;
-import com.android.launcher3.responsive.FolderSpecs;
-import com.android.launcher3.responsive.HotseatSpecs;
-import com.android.launcher3.responsive.WorkspaceSpecs;
+import com.android.launcher3.responsive.CalculatedResponsiveSpec;
+import com.android.launcher3.responsive.HotseatSpecsProvider;
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType;
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType;
+import com.android.launcher3.responsive.ResponsiveSpecsProvider;
 import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.CellContentDimensions;
 import com.android.launcher3.util.DisplayController;
@@ -84,7 +82,8 @@
 
     public static final PointF DEFAULT_SCALE = new PointF(1.0f, 1.0f);
     public static final ViewScaleProvider DEFAULT_PROVIDER = itemInfo -> DEFAULT_SCALE;
-    public static final Consumer<DeviceProfile> DEFAULT_DIMENSION_PROVIDER = dp -> {};
+    public static final Consumer<DeviceProfile> DEFAULT_DIMENSION_PROVIDER = dp -> {
+    };
 
     public final InvariantDeviceProfile inv;
     private final Info mInfo;
@@ -119,12 +118,12 @@
 
     // Responsive grid
     private final boolean mIsResponsiveGrid;
-    private CalculatedWorkspaceSpec mResponsiveWidthSpec;
-    private CalculatedWorkspaceSpec mResponsiveHeightSpec;
-    private CalculatedAllAppsSpec mAllAppsResponsiveWidthSpec;
-    private CalculatedAllAppsSpec mAllAppsResponsiveHeightSpec;
-    private CalculatedFolderSpec mResponsiveFolderWidthSpec;
-    private CalculatedFolderSpec mResponsiveFolderHeightSpec;
+    private CalculatedResponsiveSpec mResponsiveWidthSpec;
+    private CalculatedResponsiveSpec mResponsiveHeightSpec;
+    private CalculatedResponsiveSpec mAllAppsResponsiveWidthSpec;
+    private CalculatedResponsiveSpec mAllAppsResponsiveHeightSpec;
+    private CalculatedResponsiveSpec mResponsiveFolderWidthSpec;
+    private CalculatedResponsiveSpec mResponsiveFolderHeightSpec;
     private CalculatedHotseatSpec mResponsiveHotseatSpec;
 
     /**
@@ -376,7 +375,7 @@
                 res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin);
         int transientTaskbarHeight =
                 Math.round((transientTaskbarIconSize * ICON_VISIBLE_AREA_FACTOR)
-                    + (2 * res.getDimensionPixelSize(R.dimen.transient_taskbar_padding)));
+                        + (2 * res.getDimensionPixelSize(R.dimen.transient_taskbar_padding)));
         mTransientTaskbarClaimedSpace = transientTaskbarHeight + 2 * transientTaskbarBottomMargin;
 
         if (!isTaskbarPresent) {
@@ -519,12 +518,15 @@
         int minQsbMargin = res.getDimensionPixelSize(R.dimen.min_qsb_margin);
 
         if (mIsResponsiveGrid) {
-            HotseatSpecs hotseatSpecs =
-                    HotseatSpecs.create(new ResourceHelper(context,
+            float responsiveAspectRatio = (float) widthPx / heightPx;
+            HotseatSpecsProvider hotseatSpecsProvider =
+                    HotseatSpecsProvider.create(new ResourceHelper(context,
                             isTwoPanels ? inv.hotseatSpecsTwoPanelId : inv.hotseatSpecsId));
             mResponsiveHotseatSpec =
-                    isVerticalBarLayout() ? hotseatSpecs.getCalculatedWidthSpec(widthPx)
-                            : hotseatSpecs.getCalculatedHeightSpec(heightPx);
+                    isVerticalBarLayout() ? hotseatSpecsProvider.getCalculatedSpec(
+                            responsiveAspectRatio, DimensionType.WIDTH, widthPx)
+                            : hotseatSpecsProvider.getCalculatedSpec(responsiveAspectRatio,
+                                    DimensionType.HEIGHT, heightPx);
             hotseatQsbSpace = mResponsiveHotseatSpec.getHotseatQsbSpace();
             hotseatBarBottomSpace =
                     isVerticalBarLayout() ? 0 : mResponsiveHotseatSpec.getEdgePadding();
@@ -587,35 +589,46 @@
         // Needs to be calculated after hotseatBarSizePx is correct,
         // for the available height to be correct
         if (mIsResponsiveGrid) {
-            WorkspaceSpecs workspaceSpecs = WorkspaceSpecs.create(
-                    new ResourceHelper(context,
-                            isTwoPanels ? inv.workspaceSpecsTwoPanelId : inv.workspaceSpecsId));
             int availableResponsiveWidth =
                     availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0);
             int numColumns = getPanelCount() * inv.numColumns;
             // don't use availableHeightPx because it subtracts mInsets.bottom
             int availableResponsiveHeight = heightPx - mInsets.top
-                            - (isVerticalBarLayout() ? 0 : hotseatBarSizePx);
-            mResponsiveWidthSpec = workspaceSpecs.getCalculatedWidthSpec(numColumns,
-                    availableResponsiveWidth);
-            mResponsiveHeightSpec = workspaceSpecs.getCalculatedHeightSpec(inv.numRows,
-                    availableResponsiveHeight);
+                    - (isVerticalBarLayout() ? 0 : hotseatBarSizePx);
+            float responsiveAspectRatio = (float) widthPx / heightPx;
 
-            AllAppsSpecs allAppsSpecs = AllAppsSpecs.create(
+            ResponsiveSpecsProvider workspaceSpecs = ResponsiveSpecsProvider.create(
                     new ResourceHelper(context,
-                            isTwoPanels ? inv.allAppsSpecsTwoPanelId : inv.allAppsSpecsId));
-            mAllAppsResponsiveWidthSpec = allAppsSpecs.getCalculatedWidthSpec(numColumns,
-                    mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
-            mAllAppsResponsiveHeightSpec = allAppsSpecs.getCalculatedHeightSpec(inv.numRows,
-                    mResponsiveHeightSpec.getAvailableSpace(), mResponsiveHeightSpec);
+                            isTwoPanels ? inv.workspaceSpecsTwoPanelId : inv.workspaceSpecsId),
+                    ResponsiveSpecType.Workspace);
+            mResponsiveWidthSpec = workspaceSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.WIDTH, numColumns, availableResponsiveWidth);
+            mResponsiveHeightSpec = workspaceSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.HEIGHT, inv.numRows, availableResponsiveHeight);
 
-            FolderSpecs folderSpecs = FolderSpecs.create(
+            ResponsiveSpecsProvider allAppsSpecs = ResponsiveSpecsProvider.create(
                     new ResourceHelper(context,
-                            isTwoPanels ? inv.folderSpecsTwoPanelId : inv.folderSpecsId));
-            mResponsiveFolderWidthSpec = folderSpecs.getCalculatedWidthSpec(inv.numFolderColumns,
-                    mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
-            mResponsiveFolderHeightSpec = folderSpecs.getCalculatedHeightSpec(inv.numFolderRows,
-                    mResponsiveHeightSpec.getAvailableSpace(), mResponsiveHeightSpec);
+                            isTwoPanels ? inv.allAppsSpecsTwoPanelId : inv.allAppsSpecsId),
+                    ResponsiveSpecType.AllApps);
+            mAllAppsResponsiveWidthSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.WIDTH, numColumns, availableWidthPx,
+                    mResponsiveWidthSpec);
+            mAllAppsResponsiveHeightSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.HEIGHT, inv.numRows,  heightPx - mInsets.top,
+                    mResponsiveHeightSpec);
+
+            ResponsiveSpecsProvider folderSpecs = ResponsiveSpecsProvider.create(
+                    new ResourceHelper(context,
+                            isTwoPanels ? inv.folderSpecsTwoPanelId : inv.folderSpecsId),
+                    ResponsiveSpecType.Folder);
+            mResponsiveFolderWidthSpec = folderSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.WIDTH, inv.numFolderColumns,
+                    mResponsiveWidthSpec.getAvailableSpace(),
+                    mResponsiveWidthSpec);
+            mResponsiveFolderHeightSpec = folderSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.HEIGHT, inv.numFolderRows,
+                    mResponsiveHeightSpec.getAvailableSpace(),
+                    mResponsiveHeightSpec);
         }
 
         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
@@ -1258,8 +1271,12 @@
         allAppsCellHeightPx = mAllAppsResponsiveHeightSpec.getCellSizePx()
                 + mAllAppsResponsiveHeightSpec.getGutterPx();
         allAppsCellWidthPx = mAllAppsResponsiveWidthSpec.getCellSizePx();
-        allAppsPadding.left = mAllAppsResponsiveWidthSpec.getStartPaddingPx();
-        allAppsPadding.right = mAllAppsResponsiveWidthSpec.getEndPaddingPx();
+
+        // This workaround is needed to align AllApps icons with Workspace icons
+        // since AllApps doesn't have borders between cells
+        int halfBorder = allAppsBorderSpacePx.x / 2;
+        allAppsPadding.left = mAllAppsResponsiveWidthSpec.getStartPaddingPx() - halfBorder;
+        allAppsPadding.right = mAllAppsResponsiveWidthSpec.getEndPaddingPx() - halfBorder;
     }
 
     /**
@@ -1280,7 +1297,7 @@
                     + (allAppsBorderSpacePx.x * (numShownAllAppsColumns - 1))
                     + allAppsPadding.left + allAppsPadding.right;
             allAppsLeftRightMargin = Math.max(1, (availableWidthPx - usedWidth) / 2);
-        } else {
+        } else if (!mIsResponsiveGrid) {
             allAppsPadding.left = allAppsPadding.right =
                     Math.max(0, desiredWorkspaceHorizontalMarginPx + cellLayoutHorizontalPadding
                             - (allAppsBorderSpacePx.x / 2));
@@ -1338,8 +1355,6 @@
 
         if (mIsResponsiveGrid) {
             folderCellWidthPx = mResponsiveFolderWidthSpec.getCellSizePx();
-
-            // Height
             folderCellHeightPx = mResponsiveFolderHeightSpec.getCellSizePx();
             folderContentPaddingTop = mResponsiveFolderHeightSpec.getStartPaddingPx();
             folderFooterHeightPx = mResponsiveFolderHeightSpec.getEndPaddingPx();
@@ -1356,24 +1371,15 @@
 
             // Recalculating padding and cell height
             folderChildDrawablePaddingPx = getNormalizedFolderChildDrawablePaddingPx(textHeight);
-            int folderCellContentHeight = folderChildIconSizePx + folderChildDrawablePaddingPx
-                    + textHeight;
 
-            // Reduce the icon in height when it's taller than the expected cell height
-            while (folderChildIconSizePx > mIconSizeSteps.minimumIconSize()
-                    && folderCellContentHeight > folderCellHeightPx) {
-                folderChildDrawablePaddingPx -= folderCellContentHeight - folderCellHeightPx;
-                if (folderChildDrawablePaddingPx < 0) {
-                    // get a smaller icon size
-                    folderChildIconSizePx = mIconSizeSteps.getNextLowerIconSize(
-                            folderChildIconSizePx);
-                    folderChildDrawablePaddingPx =
-                            getNormalizedFolderChildDrawablePaddingPx(textHeight);
-                }
-                // calculate new cellContentHeight
-                folderCellContentHeight = folderChildIconSizePx + folderChildDrawablePaddingPx
-                        + textHeight;
-            }
+            CellContentDimensions cellContentDimensions = new CellContentDimensions(
+                    folderChildIconSizePx,
+                    folderChildDrawablePaddingPx,
+                    folderChildTextSizePx);
+            cellContentDimensions.resizeToFitCellHeight(folderCellHeightPx, mIconSizeSteps);
+            folderChildIconSizePx = cellContentDimensions.getIconSizePx();
+            folderChildDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
+            folderChildTextSizePx = cellContentDimensions.getIconTextSizePx();
         } else if (mIsScalableGrid) {
             if (inv.folderStyle == INVALID_RESOURCE_HANDLE) {
                 folderCellWidthPx = roundPxValueFromFloat(getCellSize().x * scale);
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index 78f2862..e022159 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -4,6 +4,7 @@
 import android.view.View
 import com.android.launcher3.BaseDraggingActivity.EVENT_RESUMED
 import com.android.launcher3.DropTarget.DragObject
+import com.android.launcher3.LauncherConstants.ActivityCodes
 import com.android.launcher3.SecondaryDropTarget.DeferredOnComplete
 import com.android.launcher3.dragndrop.DragLayer
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
@@ -46,7 +47,7 @@
         mLauncher.appWidgetHolder.startConfigActivity(
             mLauncher,
             widgetId,
-            Launcher.REQUEST_RECONFIGURE_APPWIDGET
+            ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET
         )
     }
 
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 8ec5c18..8ff030e 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -101,8 +101,14 @@
     }
 
     public void hideKeyboard() {
+        hideKeyboard(/* clearFocus= */ true);
+    }
+
+    public void hideKeyboard(boolean clearFocus) {
         ActivityContext.lookupContext(getContext()).hideKeyboard();
-        clearFocus();
+        if (clearFocus) {
+            clearFocus();
+        }
     }
 
     protected void onKeyboardShown() {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index edff48b..dd03035 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -33,6 +33,28 @@
 import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_CREATE_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_CREATE_SHORTCUT;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_PICK_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_CURRENT_SCREEN_IDS;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_ACTIVITY_RESULT;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_ARGS;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_CODE;
+import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_WIDGET_PANEL;
+import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_COOKIE;
+import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_METHOD_NAME;
+import static com.android.launcher3.LauncherConstants.TraceEvents.DISPLAY_ALL_APPS_TRACE_COOKIE;
+import static com.android.launcher3.LauncherConstants.TraceEvents.DISPLAY_ALL_APPS_TRACE_METHOD_NAME;
+import static com.android.launcher3.LauncherConstants.TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME;
+import static com.android.launcher3.LauncherConstants.TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE;
+import static com.android.launcher3.LauncherConstants.TraceEvents.ON_CREATE_EVT;
+import static com.android.launcher3.LauncherConstants.TraceEvents.ON_NEW_INTENT_EVT;
+import static com.android.launcher3.LauncherConstants.TraceEvents.ON_RESUME_EVT;
+import static com.android.launcher3.LauncherConstants.TraceEvents.ON_START_EVT;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -267,15 +289,6 @@
 
     static final boolean DEBUG_STRICT_MODE = false;
 
-    private static final int REQUEST_CREATE_SHORTCUT = 1;
-    private static final int REQUEST_CREATE_APPWIDGET = 5;
-
-    private static final int REQUEST_PICK_APPWIDGET = 9;
-
-    private static final int REQUEST_BIND_APPWIDGET = 11;
-    public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
-    public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
-
     private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
 
     /**
@@ -284,30 +297,9 @@
      */
     protected static final int REQUEST_LAST = 100;
 
-    // Type: int
-    protected static final String RUNTIME_STATE = "launcher.state";
-    // Type: PendingRequestArgs
-    private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
-    // Type: int
-    private static final String RUNTIME_STATE_PENDING_REQUEST_CODE = "launcher.request_code";
-    // Type: ActivityResultInfo
-    private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
-    // Type: SparseArray<Parcelable>
-    private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
-    // Type int[]
-    private static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
-
-    // Type PendingSplitSelectInfo<Parcelable>
-    protected static final String PENDING_SPLIT_SELECT_INFO = "launcher.pending_split_select_info";
-
     public static final String INTENT_ACTION_ALL_APPS_TOGGLE =
             "launcher.intent_action_all_apps_toggle";
 
-    public static final String ON_CREATE_EVT = "Launcher.onCreate";
-    public static final String ON_START_EVT = "Launcher.onStart";
-    public static final String ON_RESUME_EVT = "Launcher.onResume";
-    public static final String ON_NEW_INTENT_EVT = "Launcher.onNewIntent";
-
     private static boolean sIsNewProcess = true;
 
     private StateManager<LauncherState> mStateManager;
@@ -319,13 +311,6 @@
     private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
     @Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
 
-    private static final String DISPLAY_WORKSPACE_TRACE_METHOD_NAME = "DisplayWorkspaceFirstFrame";
-    public static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
-    public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
-    public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
-    private static final String COLD_STARTUP_TRACE_METHOD_NAME = "LauncherColdStartup";
-    public static final int COLD_STARTUP_TRACE_COOKIE = 2;
-
     private static final FloatProperty<Workspace<?>> WORKSPACE_WIDGET_SCALE =
             WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
     private static final FloatProperty<Hotseat> HOTSEAT_WIDGET_SCALE =
@@ -2121,7 +2106,7 @@
 
     @Override
     public void bindScreens(IntArray orderedScreenIds) {
-        mWorkspace.mPageIndicator.setAreScreensBinding(true);
+        mWorkspace.mPageIndicator.setAreScreensBinding(true, mDeviceProfile.isTwoPanels);
         int firstScreenPosition = 0;
         if ((FeatureFlags.QSB_ON_FIRST_SCREEN
                 && mIsFirstPagePinnedItemEnabled
@@ -2650,7 +2635,7 @@
 
         TraceHelper.INSTANCE.endSection();
         mWorkspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true);
-        mWorkspace.mPageIndicator.setAreScreensBinding(false);
+        mWorkspace.mPageIndicator.setAreScreensBinding(false, mDeviceProfile.isTwoPanels);
     }
 
     private boolean canAnimatePageChange() {
@@ -2803,10 +2788,8 @@
             Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
         mModelCallbacks.bindAllApplications(apps, flags, packageUserKeytoUidMap);
         if (Utilities.ATLEAST_S) {
-            Trace.endAsyncSection(
-                    Launcher.DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
-                    Launcher.DISPLAY_ALL_APPS_TRACE_COOKIE
-            );
+            Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
+                    DISPLAY_ALL_APPS_TRACE_COOKIE);
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java
new file mode 100644
index 0000000..1abfeb9
--- /dev/null
+++ b/src/com/android/launcher3/LauncherConstants.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+public class LauncherConstants {
+
+    /**
+     * Trace events to visualize using Systrace tool.
+     */
+    public static class TraceEvents {
+
+        public static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
+        public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
+        public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
+        public static final int COLD_STARTUP_TRACE_COOKIE = 2;
+        public static final String ON_CREATE_EVT = "Launcher.onCreate";
+        public static final String ON_START_EVT = "Launcher.onStart";
+        public static final String ON_RESUME_EVT = "Launcher.onResume";
+        public static final String ON_NEW_INTENT_EVT = "Launcher.onNewIntent";
+        static final String DISPLAY_WORKSPACE_TRACE_METHOD_NAME = "DisplayWorkspaceFirstFrame";
+        static final String COLD_STARTUP_TRACE_METHOD_NAME = "LauncherColdStartup";
+    }
+
+    /**
+     * This are the different codes the Launcher can receive when a new Launcher Intent is created.
+     */
+    public static class ActivityCodes {
+
+        public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
+        public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
+        static final int REQUEST_CREATE_SHORTCUT = 1;
+        static final int REQUEST_CREATE_APPWIDGET = 5;
+        static final int REQUEST_PICK_APPWIDGET = 9;
+        static final int REQUEST_BIND_APPWIDGET = 11;
+    }
+
+    /**
+     * Keys used to get the saved values of the previous Activity instance.
+     */
+    public static class SavedInstanceKeys {
+
+        // Type: int
+        public static final String RUNTIME_STATE = "launcher.state";
+        // Type PendingSplitSelectInfo<Parcelable>
+        public static final String PENDING_SPLIT_SELECT_INFO = "launcher.pending_split_select_info";
+        // Type: PendingRequestArgs
+        static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
+        // Type: int
+        static final String RUNTIME_STATE_PENDING_REQUEST_CODE = "launcher.request_code";
+        // Type: ActivityResultInfo
+        static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
+        // Type: SparseArray<Parcelable>
+        static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
+        // Type int[]
+        static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
+    }
+}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index fd8f668..551735e 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -20,6 +20,8 @@
 
 import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
 import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
+import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_AVAILABLE;
+import static com.android.launcher3.pm.UserCache.ACTION_PROFILE_UNAVAILABLE;
 import static com.android.launcher3.testing.shared.TestProtocol.sDebugTracing;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -326,6 +328,16 @@
         } else if (UserCache.ACTION_PROFILE_ADDED.equals(action)
                 || UserCache.ACTION_PROFILE_REMOVED.equals(action)) {
             forceReload();
+        } else if (ACTION_PROFILE_AVAILABLE.equals(action)
+                || ACTION_PROFILE_UNAVAILABLE.equals(action)) {
+            /*
+             * This broadcast is only available when android.os.Flags.allowPrivateProfile() is set.
+             * For Work-profile this broadcast will be sent in addition to
+             * ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE.
+             * So effectively, this if block only handles the non-work profile case.
+             */
+            enqueueModelUpdateTask(new PackageUpdatedTask(
+                    PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user));
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 4f8caab..aa06089 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -334,21 +334,21 @@
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
                 nonRestorableItem(
                         "pref_long_press_nav_handle_haptic_hint_end_scale_percent",
-                        50,
+                        100,
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
                 )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
                 nonRestorableItem(
                         "pref_long_press_nav_handle_haptic_hint_scale_exponent",
-                        2,
+                        1,
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
                 )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
                 nonRestorableItem(
                         "pref_long_press_nav_handle_haptic_hint_iterations",
-                        40,
+                        50,
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
                 )
         @JvmField
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 84d3805..2315111 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -49,6 +49,7 @@
 import android.animation.ValueAnimator;
 import android.util.FloatProperty;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.LauncherState.PageAlphaProvider;
@@ -164,6 +165,8 @@
                 state.hasFlag(FLAG_HOTSEAT_INACCESSIBLE)
                         ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                         : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+        hotseat.setDescendantFocusability(state.hasFlag(FLAG_HOTSEAT_INACCESSIBLE)
+                ? ViewGroup.FOCUS_BLOCK_DESCENDANTS : ViewGroup.FOCUS_BEFORE_DESCENDANTS);
 
         Interpolator translationInterpolator =
                 config.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 72c6cb8..5b4d2b8 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -80,6 +80,7 @@
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.Themes;
@@ -203,7 +204,9 @@
 
         mWorkManager = new WorkProfileManager(
                 mActivityContext.getSystemService(UserManager.class),
-                this, mActivityContext.getStatsLogManager());
+                this,
+                mActivityContext.getStatsLogManager(),
+                UserCache.INSTANCE.get(mActivityContext));
         mAH = Arrays.asList(null, null, null);
         mNavBarScrimPaint = new Paint();
         mNavBarScrimPaint.setColor(Themes.getNavBarScrimColor(mActivityContext));
@@ -1098,7 +1101,7 @@
         mlp.rightMargin = insets.right;
         setLayoutParams(mlp);
 
-        if (grid.isVerticalBarLayout()) {
+        if (grid.isVerticalBarLayout() && !FeatureFlags.enableResponsiveWorkspace()) {
             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
         } else {
             int topPadding = grid.allAppsPadding.top;
@@ -1108,7 +1111,6 @@
             }
             setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0);
         }
-
         InsettableFrameLayout.dispatchInsets(this, insets);
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 9f6e0fc..051cf50 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -124,6 +124,9 @@
      * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_QUIET_MODE_ENABLED
      * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
      * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
+     * @see com.android.launcher3.model.BgDataModel.Callbacks#FLAG_WORK_PROFILE_QUIET_MODE_ENABLED
+     * @see
+     * com.android.launcher3.model.BgDataModel.Callbacks#FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED
      */
     public boolean hasModelFlag(int mask) {
         return (mModelFlags & mask) != 0;
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 005e6df..7baf7d3 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -27,7 +27,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.config.FeatureFlags;
@@ -213,6 +212,7 @@
                 BubbleTextView icon = (BubbleTextView) holder.itemView;
                 icon.reset();
                 icon.applyFromApplicationInfo(adapterItem.itemInfo);
+                icon.setOnFocusChangeListener(mIconFocusListener);
                 break;
             }
             case VIEW_TYPE_EMPTY_SEARCH: {
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index ac0e5a4..61c3d3f 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -25,10 +25,10 @@
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.os.Build;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -40,12 +40,14 @@
 import androidx.annotation.RequiresApi;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip;
 
 import java.lang.annotation.Retention;
@@ -84,16 +86,19 @@
 
     private WorkModeSwitch mWorkModeSwitch;
 
+    private final UserCache mUserCache;
+
     @WorkProfileState
     private int mCurrentState;
 
     public WorkProfileManager(
             UserManager userManager, ActivityAllAppsContainerView allApps,
-            StatsLogManager statsLogManager) {
+            StatsLogManager statsLogManager, UserCache userCache) {
         mUserManager = userManager;
         mAllApps = allApps;
-        mMatcher = mAllApps.mPersonalMatcher.negate();
         mStatsLogManager = statsLogManager;
+        mUserCache = userCache;
+        mMatcher = info -> info != null && mUserCache.getUserInfo(info.user).isWork();
     }
 
     /**
@@ -103,11 +108,11 @@
     public void setWorkProfileEnabled(boolean enabled) {
         updateCurrentState(STATE_TRANSITION);
         UI_HELPER_EXECUTOR.post(() -> {
-            for (UserHandle userProfile : mUserManager.getUserProfiles()) {
-                if (Process.myUserHandle().equals(userProfile)) {
-                    continue;
+            for (UserHandle userProfile : mUserCache.getUserProfiles()) {
+                if (mUserCache.getUserInfo(userProfile).isWork()) {
+                    mUserManager.requestQuietModeEnabled(!enabled, userProfile);
+                    break;
                 }
-                mUserManager.requestQuietModeEnabled(!enabled, userProfile);
             }
         });
     }
@@ -131,7 +136,13 @@
      * Requests work profile state from {@link AllAppsStore} and updates work profile related views
      */
     public void reset() {
-        boolean isEnabled = !mAllApps.getAppsStore().hasModelFlag(FLAG_QUIET_MODE_ENABLED);
+        int quietModeFlag;
+        if (Flags.enablePrivateSpace()) {
+            quietModeFlag = FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
+        } else {
+            quietModeFlag = FLAG_QUIET_MODE_ENABLED;
+        }
+        boolean isEnabled = !mAllApps.getAppsStore().hasModelFlag(quietModeFlag);
         updateCurrentState(isEnabled ? STATE_ENABLED : STATE_DISABLED);
         if (mWorkModeSwitch != null) {
             // reset the position of the button and clear IME insets.
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4e7ec0b..73861c1 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -119,6 +119,10 @@
             getReleaseFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
                     "Add dev options to customize the LPNH trigger slop and milliseconds");
 
+    public static final BooleanFlag ANIMATE_LPNH =
+            getReleaseFlag(308693847, "ANIMATE_LPNH", TEAMFOOD,
+                    "Animates navbar when long pressing");
+
     public static final IntFlag LPNH_SLOP_PERCENTAGE =
             getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
                     "Controls touch slop percentage for lpnh");
@@ -271,12 +275,12 @@
                     "Enables long pressing on the bottom bar nav handle to trigger events.");
 
     public static final BooleanFlag ENABLE_SEARCH_HAPTIC_HINT =
-            getReleaseFlag(303023676, "ENABLE_SEARCH_HAPTIC_HINT", TEAMFOOD,
+            getReleaseFlag(303023676, "ENABLE_SEARCH_HAPTIC_HINT", ENABLED,
                     "Enables haptic hint when long pressing on the bottom bar nav handle.");
 
     // TODO(Block 17): Clean up flags
     public static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
-            "ENABLE_TASKBAR_PINNING", DISABLED,
+            "ENABLE_TASKBAR_PINNING", TEAMFOOD,
             "Enables taskbar pinning to allow user to switch between transient and persistent "
                     + "taskbar flavors");
 
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 18200f6..69fa673 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -169,6 +169,9 @@
                 LauncherPrefs.get(getContext())
                         .put(THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE));
                 getContext().getContentResolver().notifyChange(uri, null);
+                mActivePreviews.values().forEach(observer ->
+                        observer.renderer.refreshIcons()
+                );
                 return 1;
             }
             default:
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 3330448..346f644 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -35,9 +35,12 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.res.TypedArray;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -76,6 +79,7 @@
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -83,6 +87,7 @@
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
@@ -188,6 +193,8 @@
     private final SparseIntArray mWallpaperColorResources;
     private final SparseArray<Size> mLauncherWidgetSpanInfo;
 
+    private final Map<BubbleTextView, WorkspaceItemInfo> mIcons = new HashMap<>();
+
     public LauncherPreviewRenderer(Context context,
             InvariantDeviceProfile idp,
             WallpaperColors wallpaperColorsOverride,
@@ -366,11 +373,43 @@
         return CellPosMapper.DEFAULT;
     }
 
+    /**
+     * Refreshes icon to update based on resource changes.
+     */
+    public void refreshIcons() {
+        mUiHandler.post(() -> {
+                    IconProvider iconProvider = null;
+                    int iconDpi = -1;
+                    for (Map.Entry<BubbleTextView, WorkspaceItemInfo> entry : mIcons.entrySet()) {
+                        BubbleTextView icon = entry.getKey();
+                        ItemInfoWithIcon info = entry.getValue();
+                        // get monochrome themed icon if it was not initially cached
+                        if (info.bitmap.getMono() == null) {
+                            if (iconProvider == null || iconDpi == -1) {
+                                LauncherAppState appState = LauncherAppState.getInstance(mContext);
+                                iconProvider = appState.getIconProvider();
+                                iconDpi = appState.getInvariantDeviceProfile().fillResIconDpi;
+                            }
+                            LauncherActivityInfo activityInfo = mContext.getSystemService(
+                                            LauncherApps.class)
+                                    .resolveActivity(info.getIntent(), info.user);
+                            Drawable iconDrawable = iconProvider.getIcon(activityInfo,  iconDpi);
+                            LauncherIcons iconFactory = LauncherIcons.obtain(mContext);
+                            info.bitmap = iconFactory.createBadgedIconBitmap(iconDrawable);
+                        }
+                        // update icon based on whether themed icon is enabled
+                        icon.reapplyItemInfo(info);
+                    }
+                }
+        );
+    }
+
     private void inflateAndAddIcon(WorkspaceItemInfo info) {
         CellLayout screen = mWorkspaceScreens.get(info.screenId);
         BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
                 R.layout.app_icon, screen, false);
         icon.applyFromWorkspaceItem(info);
+        mIcons.put(icon, info);
         addInScreenFromBind(icon, info);
     }
 
@@ -477,6 +516,8 @@
                 currentWorkspaceItems, otherWorkspaceItems);
         filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
                 otherAppWidgets);
+
+        mIcons.clear();
         for (ItemInfo itemInfo : currentWorkspaceItems) {
             switch (itemInfo.itemType) {
                 case Favorites.ITEM_TYPE_APPLICATION:
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 683354b..1bac765 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -201,6 +201,13 @@
         }
     }
 
+    /**
+     * Refreshes icon to update based on resource changes.
+     */
+    public void refreshIcons() {
+        mRenderer.refreshIcons();
+    }
+
     /***
      * Generates a new context overriding the theme color and the display size without affecting the
      * main application context
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 8f85bfb..190eb78 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -79,6 +79,8 @@
      * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
      * @see Callbacks#FLAG_QUIET_MODE_ENABLED
      * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
+     * @see Callbacks#FLAG_WORK_PROFILE_QUIET_MODE_ENABLED
+     * @see Callbacks#FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED
      */
     private int mFlags;
 
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 54ecc00..7f0f683 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -477,6 +477,10 @@
         int FLAG_QUIET_MODE_ENABLED = 1 << 1;
         // If launcher can change quiet mode
         int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
+        // If quiet mode is enabled for work profile user
+        int FLAG_WORK_PROFILE_QUIET_MODE_ENABLED = 1 << 3;
+        // If quiet mode is enabled for private profile user
+        int FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED = 1 << 4;
 
         /**
          * Returns an IntSet of page ids to bind first, synchronously if possible
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 1ab0355..f4ce360 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -23,8 +23,10 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
@@ -57,8 +59,10 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
@@ -143,7 +147,7 @@
     private final InstallSessionHelper mSessionHelper;
     private final IconCache mIconCache;
 
-    private final UserManagerState mUserManagerState = new UserManagerState();
+    private final UserManagerState mUserManagerState;
 
     protected final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap = new ArrayMap<>();
     private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts;
@@ -156,6 +160,13 @@
 
     public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
             ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder) {
+        this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, new UserManagerState());
+    }
+
+    @VisibleForTesting
+    LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
+            ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder,
+            UserManagerState userManagerState) {
         mApp = app;
         mBgAllAppsList = bgAllAppsList;
         mBgDataModel = bgModel;
@@ -164,9 +175,10 @@
 
         mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
         mUserManager = mApp.getContext().getSystemService(UserManager.class);
-        mUserCache = UserCache.INSTANCE.get(mApp.getContext());
+        mUserCache = UserCache.getInstance(mApp.getContext());
         mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
         mIconCache = mApp.getIconCache();
+        mUserManagerState = userManagerState;
     }
 
     protected synchronized void waitForIdle() {
@@ -973,6 +985,8 @@
         mBgAllAppsList.clear();
 
         List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
+        boolean isWorkProfileQuiet = false;
+        boolean isPrivateProfileQuiet = false;
         for (UserHandle user : profiles) {
             // Query for the set of apps
             final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
@@ -982,6 +996,14 @@
                 return allActivityList;
             }
             boolean quietMode = mUserManagerState.isUserQuiet(user);
+
+            if (Flags.enablePrivateSpace()) {
+                if (mUserCache.getUserInfo(user).isWork()) {
+                    isWorkProfileQuiet = quietMode;
+                } else if (mUserCache.getUserInfo(user).isPrivate()) {
+                    isPrivateProfileQuiet = quietMode;
+                }
+            }
             // Create the ApplicationInfos
             for (int i = 0; i < apps.size(); i++) {
                 LauncherActivityInfo app = apps.get(i);
@@ -1023,8 +1045,13 @@
             Trace.endSection();
         }
 
-        mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
-                mUserManagerState.isAnyProfileQuietModeEnabled());
+        if (Flags.enablePrivateSpace()) {
+            mBgAllAppsList.setFlags(FLAG_WORK_PROFILE_QUIET_MODE_ENABLED, isWorkProfileQuiet);
+            mBgAllAppsList.setFlags(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED, isPrivateProfileQuiet);
+        } else {
+            mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
+                    mUserManagerState.isAnyProfileQuietModeEnabled());
+        }
         mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION,
                 hasShortcutsPermission(mApp.getContext()));
         mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION,
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 37a7171..9a0a6eb 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,7 +15,9 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
 
@@ -31,6 +33,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -169,14 +172,24 @@
                 break;
             case OP_USER_AVAILABILITY_CHANGE: {
                 UserManagerState ums = new UserManagerState();
-                ums.init(UserCache.INSTANCE.get(context),
-                        context.getSystemService(UserManager.class));
+                UserManager userManager = context.getSystemService(UserManager.class);
+                ums.init(UserCache.INSTANCE.get(context), userManager);
+                boolean isUserQuiet =  ums.isUserQuiet(mUser);
                 flagOp = FlagOp.NO_OP.setFlag(
-                        WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER, ums.isUserQuiet(mUser));
+                        WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER, isUserQuiet);
                 appsList.updateDisabledFlags(matcher, flagOp);
 
-                // We are not synchronizing here, as int operations are atomic
-                appsList.setFlags(FLAG_QUIET_MODE_ENABLED, ums.isAnyProfileQuietModeEnabled());
+                if (Flags.enablePrivateSpace()) {
+                    UserCache userCache = UserCache.INSTANCE.get(context);
+                    if (userCache.getUserInfo(mUser).isWork()) {
+                        appsList.setFlags(FLAG_WORK_PROFILE_QUIET_MODE_ENABLED, isUserQuiet);
+                    } else if (userCache.getUserInfo(mUser).isPrivate()) {
+                        appsList.setFlags(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED, isUserQuiet);
+                    }
+                } else {
+                    // We are not synchronizing here, as int operations are atomic
+                    appsList.setFlags(FLAG_QUIET_MODE_ENABLED, ums.isAnyProfileQuietModeEnabled());
+                }
                 break;
             }
             default:
diff --git a/src/com/android/launcher3/model/UserManagerState.java b/src/com/android/launcher3/model/UserManagerState.java
index 97a5905..720f08e 100644
--- a/src/com/android/launcher3/model/UserManagerState.java
+++ b/src/com/android/launcher3/model/UserManagerState.java
@@ -61,6 +61,9 @@
 
     /**
      * Returns true if any user profile has quiet mode enabled.
+     * <p>
+     * Do not use this for determining if a specific profile has quiet mode enabled, as their can
+     * be more than one profile in quiet mode.
      */
     public boolean isAnyProfileQuietModeEnabled() {
         for (int i = mQuietUsersHashCodeMap.size() - 1; i >= 0; i--) {
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
index 4ab2037..30156c8 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -30,7 +30,7 @@
      * Sets flag to indicate when the screens are in the process of binding so that we don't animate
      * during that period.
      */
-    default void setAreScreensBinding(boolean areScreensBinding) {
+    default void setAreScreensBinding(boolean areScreensBinding, boolean isTwoPanels) {
         // No-op by default
     }
 
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 323b3a7..fd1b64f 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.pageindicators;
 
+import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -129,6 +131,7 @@
     private float mCurrentPosition;
     private float mFinalPosition;
     private boolean mAreScreensBinding;
+    private boolean mIsTwoPanels;
     private ObjectAnimator mAnimator;
     private @Nullable ObjectAnimator mAlphaAnimator;
 
@@ -348,6 +351,12 @@
 
     @Override
     public void setActiveMarker(int activePage) {
+        // In unfolded foldables, every page has two CellLayouts, so we need to halve the active
+        // page for it to be accurate.
+        if (mIsTwoPanels && !FOLDABLE_SINGLE_PAGE.get()) {
+            activePage = activePage / 2;
+        }
+
         if (mActivePage != activePage) {
             mActivePage = activePage;
         }
@@ -360,7 +369,9 @@
     }
 
     @Override
-    public void setAreScreensBinding(boolean areScreensBinding) {
+    public void setAreScreensBinding(boolean areScreensBinding, boolean isTwoPanels) {
+        mIsTwoPanels = isTwoPanels;
+
         // Reapply correct current position which was skipped during setScroll.
         if (mAreScreensBinding && !areScreensBinding) {
             CURRENT_POSITION.set(this, (float) mActivePage);
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index e2b1286..4661fd4 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -55,10 +55,18 @@
             ? Intent.ACTION_PROFILE_ACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNLOCKED;
     public static final String ACTION_PROFILE_LOCKED = ATLEAST_U
             ? Intent.ACTION_PROFILE_INACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
+    public static final String ACTION_PROFILE_AVAILABLE = "android.intent.action.PROFILE_AVAILABLE";
+    public static final String ACTION_PROFILE_UNAVAILABLE =
+            "android.intent.action.PROFILE_UNAVAILABLE";
 
     public static final MainThreadInitializedObject<UserCache> INSTANCE =
             new MainThreadInitializedObject<>(UserCache::new);
 
+    /** Returns an instance of UserCache bound to the context provided. */
+    public static UserCache getInstance(Context context) {
+        return INSTANCE.get(context);
+    }
+
     private final List<BiConsumer<UserHandle, String>> mUserEventListeners = new ArrayList<>();
     private final SimpleBroadcastReceiver mUserChangeReceiver =
             new SimpleBroadcastReceiver(this::onUsersChanged);
@@ -87,7 +95,9 @@
                 ACTION_PROFILE_ADDED,
                 ACTION_PROFILE_REMOVED,
                 ACTION_PROFILE_UNLOCKED,
-                ACTION_PROFILE_LOCKED);
+                ACTION_PROFILE_LOCKED,
+                ACTION_PROFILE_AVAILABLE,
+                ACTION_PROFILE_UNAVAILABLE);
         updateCache();
     }
 
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 6b08153..e3314d4 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -127,6 +127,7 @@
 
     protected final float mElevation;
 
+    // Tag for Views that have children that will need to be iterated to add styling.
     private final String mIterateChildrenTag;
 
     protected final int[] mColorIds;
@@ -244,7 +245,7 @@
         for (int i = 0; i < count; i++) {
             View view = viewGroup.getChildAt(i);
             if (view.getVisibility() == VISIBLE) {
-                if (lastView != null) {
+                if (lastView != null && (isShortcutContainer(lastView))) {
                     MarginLayoutParams mlp = (MarginLayoutParams) lastView.getLayoutParams();
                     mlp.bottomMargin = mChildContainerMargin;
                 }
@@ -415,8 +416,7 @@
     private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) {
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
 
-        int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical
-                + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+        int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical + getExtraVerticalOffset();
         // The margins are added after we call this method, so we need to account for them here.
         int numVisibleChildren = 0;
         for (int i = getChildCount() - 1; i >= 0; --i) {
@@ -632,6 +632,10 @@
         mOpenCloseAnimator.start();
     }
 
+    public int getExtraVerticalOffset() {
+        return getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+    }
+
     protected AnimatorSet getOpenCloseAnimator(boolean isOpening, int scaleDuration,
             int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration,
             Interpolator interpolator) {
diff --git a/src/com/android/launcher3/responsive/AllAppsSpecs.kt b/src/com/android/launcher3/responsive/AllAppsSpecs.kt
deleted file mode 100644
index 8ed3ffc..0000000
--- a/src/com/android/launcher3/responsive/AllAppsSpecs.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.responsive
-
-import android.content.res.TypedArray
-import com.android.launcher3.R
-import com.android.launcher3.responsive.ResponsiveSpec.SpecType
-import com.android.launcher3.util.ResourceHelper
-
-class AllAppsSpecs(widthSpecs: List<AllAppsSpec>, heightSpecs: List<AllAppsSpec>) :
-    ResponsiveSpecs<AllAppsSpec>(widthSpecs, heightSpecs) {
-
-    fun getCalculatedWidthSpec(
-        columns: Int,
-        availableWidth: Int,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-    ): CalculatedAllAppsSpec {
-        check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
-            "Invalid specType for CalculatedWorkspaceSpec. " +
-                "Expected: ${SpecType.WIDTH} - " +
-                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
-        }
-
-        val spec = getWidthSpec(availableWidth)
-        return CalculatedAllAppsSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
-    }
-
-    fun getCalculatedHeightSpec(
-        rows: Int,
-        availableHeight: Int,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-    ): CalculatedAllAppsSpec {
-        check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
-            "Invalid specType for CalculatedWorkspaceSpec. " +
-                "Expected: ${SpecType.HEIGHT} - " +
-                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
-        }
-
-        val spec = getHeightSpec(availableHeight)
-        return CalculatedAllAppsSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
-    }
-
-    companion object {
-        private const val XML_ALL_APPS_SPEC = "allAppsSpec"
-
-        @JvmStatic
-        fun create(resourceHelper: ResourceHelper): AllAppsSpecs {
-            val parser = ResponsiveSpecsParser(resourceHelper)
-            val specs = parser.parseXML(XML_ALL_APPS_SPEC, ::AllAppsSpec)
-            val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
-            return AllAppsSpecs(widthSpecs, heightSpecs)
-        }
-    }
-}
-
-data class AllAppsSpec(
-    override val maxAvailableSize: Int,
-    override val specType: SpecType,
-    override val startPadding: SizeSpec,
-    override val endPadding: SizeSpec,
-    override val gutter: SizeSpec,
-    override val cellSize: SizeSpec
-) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
-
-    init {
-        check(isValid()) { "Invalid AllAppsSpec found." }
-    }
-
-    constructor(
-        attrs: TypedArray,
-        specs: Map<String, SizeSpec>
-    ) : this(
-        maxAvailableSize =
-            attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
-        specType =
-            SpecType.values()[
-                    attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
-        startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
-        endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
-        gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
-        cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
-    )
-}
-
-class CalculatedAllAppsSpec(
-    availableSpace: Int,
-    cells: Int,
-    spec: AllAppsSpec,
-    calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
diff --git a/src/com/android/launcher3/responsive/FolderSpecs.kt b/src/com/android/launcher3/responsive/FolderSpecs.kt
deleted file mode 100644
index bc2db28..0000000
--- a/src/com/android/launcher3/responsive/FolderSpecs.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.responsive
-
-import android.content.res.TypedArray
-import com.android.launcher3.R
-import com.android.launcher3.responsive.ResponsiveSpec.SpecType
-import com.android.launcher3.util.ResourceHelper
-
-class FolderSpecs(widthSpecs: List<FolderSpec>, heightSpecs: List<FolderSpec>) :
-    ResponsiveSpecs<FolderSpec>(widthSpecs, heightSpecs) {
-
-    fun getCalculatedWidthSpec(
-        columns: Int,
-        availableWidth: Int,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-    ): CalculatedFolderSpec {
-        check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
-            "Invalid specType for CalculatedWorkspaceSpec. " +
-                "Expected: ${SpecType.WIDTH} - " +
-                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
-        }
-
-        val spec = getWidthSpec(availableWidth)
-        return CalculatedFolderSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
-    }
-
-    fun getCalculatedHeightSpec(
-        rows: Int,
-        availableHeight: Int,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-    ): CalculatedFolderSpec {
-        check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
-            "Invalid specType for CalculatedWorkspaceSpec. " +
-                "Expected: ${SpecType.HEIGHT} - " +
-                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
-        }
-
-        val spec = getHeightSpec(availableHeight)
-        return CalculatedFolderSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
-    }
-
-    companion object {
-
-        private const val XML_FOLDER_SPEC = "folderSpec"
-
-        @JvmStatic
-        fun create(resourceHelper: ResourceHelper): FolderSpecs {
-            val parser = ResponsiveSpecsParser(resourceHelper)
-            val specs = parser.parseXML(XML_FOLDER_SPEC, ::FolderSpec)
-            val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
-            return FolderSpecs(widthSpecs, heightSpecs)
-        }
-    }
-}
-
-data class FolderSpec(
-    override val maxAvailableSize: Int,
-    override val specType: SpecType,
-    override val startPadding: SizeSpec,
-    override val endPadding: SizeSpec,
-    override val gutter: SizeSpec,
-    override val cellSize: SizeSpec
-) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
-
-    init {
-        check(isValid()) { "Invalid FolderSpec found." }
-    }
-
-    constructor(
-        attrs: TypedArray,
-        specs: Map<String, SizeSpec>
-    ) : this(
-        maxAvailableSize =
-            attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
-        specType =
-            SpecType.values()[
-                    attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
-        startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
-        endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
-        gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
-        cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
-    )
-}
-
-class CalculatedFolderSpec(
-    availableSpace: Int,
-    cells: Int,
-    spec: FolderSpec,
-    calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
diff --git a/src/com/android/launcher3/responsive/HotseatSpecs.kt b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
similarity index 70%
rename from src/com/android/launcher3/responsive/HotseatSpecs.kt
rename to src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
index 37a682f..7f7f2f1 100644
--- a/src/com/android/launcher3/responsive/HotseatSpecs.kt
+++ b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
@@ -19,67 +19,65 @@
 import android.content.res.TypedArray
 import android.util.Log
 import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
 import com.android.launcher3.util.ResourceHelper
 
-class HotseatSpecs(widthSpecs: List<HotseatSpec>, heightSpecs: List<HotseatSpec>) {
+class HotseatSpecsProvider(private val groupOfSpecs: List<ResponsiveSpecGroup<HotseatSpec>>) {
+    fun getSpecsByAspectRatio(aspectRatio: Float): ResponsiveSpecGroup<HotseatSpec> {
+        check(aspectRatio > 0f) { "Invalid aspect ratio! The value should be bigger than 0." }
 
-    val widthSpecs: List<HotseatSpec>
-    val heightSpecs: List<HotseatSpec>
+        val specsGroup = groupOfSpecs.firstOrNull { aspectRatio <= it.aspectRatio }
+        check(specsGroup != null) { "No available spec with aspectRatio within $aspectRatio." }
 
-    init {
-        this.widthSpecs = widthSpecs.sortedBy { it.maxAvailableSize }
-        this.heightSpecs = heightSpecs.sortedBy { it.maxAvailableSize }
+        return specsGroup
     }
 
-    fun getCalculatedHeightSpec(availableHeight: Int): CalculatedHotseatSpec {
-        val spec = heightSpecs.firstOrNull { availableHeight <= it.maxAvailableSize }
-        check(spec != null) { "No available height spec found within $availableHeight." }
-        return CalculatedHotseatSpec(availableHeight, spec)
-    }
-
-    fun getCalculatedWidthSpec(availableWidth: Int): CalculatedHotseatSpec {
-        val spec = widthSpecs.firstOrNull { availableWidth <= it.maxAvailableSize }
-        check(spec != null) { "No available width spec found within $availableWidth." }
-        return CalculatedHotseatSpec(availableWidth, spec)
+    fun getCalculatedSpec(
+        aspectRatio: Float,
+        dimensionType: DimensionType,
+        availableSpace: Int
+    ): CalculatedHotseatSpec {
+        val specsGroup = getSpecsByAspectRatio(aspectRatio)
+        val spec = specsGroup.getSpec(dimensionType, availableSpace)
+        return CalculatedHotseatSpec(availableSpace, spec)
     }
 
     companion object {
-        private const val XML_HOTSEAT_SPEC = "hotseatSpec"
-
         @JvmStatic
-        fun create(resourceHelper: ResourceHelper): HotseatSpecs {
+        fun create(resourceHelper: ResourceHelper): HotseatSpecsProvider {
             val parser = ResponsiveSpecsParser(resourceHelper)
-            val specs = parser.parseXML(XML_HOTSEAT_SPEC, ::HotseatSpec)
-            val (widthSpecs, heightSpecs) =
-                specs.partition { it.specType == ResponsiveSpec.SpecType.WIDTH }
-            return HotseatSpecs(widthSpecs, heightSpecs)
+            val specs = parser.parseXML(ResponsiveSpecType.Hotseat, ::HotseatSpec)
+            return HotseatSpecsProvider(specs)
         }
     }
 }
 
 data class HotseatSpec(
-    val maxAvailableSize: Int,
-    val specType: ResponsiveSpec.SpecType,
+    override val maxAvailableSize: Int,
+    override val dimensionType: DimensionType,
+    override val specType: ResponsiveSpecType,
     val hotseatQsbSpace: SizeSpec,
     val edgePadding: SizeSpec
-) {
-
+) : IResponsiveSpec {
     init {
         check(isValid()) { "Invalid HotseatSpec found." }
     }
 
     constructor(
+        responsiveSpecType: ResponsiveSpecType,
         attrs: TypedArray,
         specs: Map<String, SizeSpec>
     ) : this(
         maxAvailableSize =
             attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
-        specType =
-            ResponsiveSpec.SpecType.values()[
+        dimensionType =
+            DimensionType.entries[
                     attrs.getInt(
                         R.styleable.ResponsiveSpec_specType,
-                        ResponsiveSpec.SpecType.HEIGHT.ordinal
+                        DimensionType.HEIGHT.ordinal
                     )],
+        specType = responsiveSpecType,
         hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE),
         edgePadding = specs.getOrError(SizeSpec.XmlTags.EDGE_PADDING)
     )
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecs.kt b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
similarity index 64%
rename from src/com/android/launcher3/responsive/ResponsiveSpecs.kt
rename to src/com/android/launcher3/responsive/ResponsiveSpec.kt
index a43c44a..cf70ad2 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpecs.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
@@ -16,75 +16,84 @@
 
 package com.android.launcher3.responsive
 
+import android.content.res.TypedArray
 import android.util.Log
+import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 
 /**
- * Base class for responsive specs that holds a list of width and height specs.
+ * Interface for responsive grid specs
  *
- * @param widthSpecs List of width responsive specifications
- * @param heightSpecs List of height responsive specifications
+ * @property maxAvailableSize indicates the breakpoint to use this specification.
+ * @property dimensionType indicates whether the paddings and gutters will be applied vertically or
+ *   horizontally.
+ * @property specType a [ResponsiveSpecType] that indicates the type of the spec.
  */
-abstract class ResponsiveSpecs<T : ResponsiveSpec>(widthSpecs: List<T>, heightSpecs: List<T>) {
-    val widthSpecs: List<T>
-    val heightSpecs: List<T>
-
-    init {
-        check(widthSpecs.isNotEmpty() && heightSpecs.isNotEmpty()) {
-            "${this::class.simpleName} is incomplete - " +
-                "width list size = ${widthSpecs.size}; " +
-                "height list size = ${heightSpecs.size}."
-        }
-
-        this.widthSpecs = widthSpecs.sortedBy { it.maxAvailableSize }
-        this.heightSpecs = heightSpecs.sortedBy { it.maxAvailableSize }
-    }
-
-    /**
-     * Get a [ResponsiveSpec] for width within the breakpoint.
-     *
-     * @param availableWidth The width breakpoint for the spec
-     * @return A [ResponsiveSpec] for width.
-     */
-    fun getWidthSpec(availableWidth: Int): T {
-        val spec = widthSpecs.firstOrNull { availableWidth <= it.maxAvailableSize }
-        check(spec != null) { "No available width spec found within $availableWidth." }
-        return spec
-    }
-
-    /**
-     * Get a [ResponsiveSpec] for height within the breakpoint.
-     *
-     * @param availableHeight The height breakpoint for the spec
-     * @return A [ResponsiveSpec] for height.
-     */
-    fun getHeightSpec(availableHeight: Int): T {
-        val spec = heightSpecs.firstOrNull { availableHeight <= it.maxAvailableSize }
-        check(spec != null) { "No available height spec found within $availableHeight." }
-        return spec
-    }
+interface IResponsiveSpec {
+    val maxAvailableSize: Int
+    val dimensionType: ResponsiveSpec.DimensionType
+    val specType: ResponsiveSpecType
 }
 
 /**
- * Base class for a responsive specification that is used to calculate the paddings, gutter and cell
+ * Class for a responsive specification that is used to calculate the paddings, gutter and cell
  * size.
  *
  * @param maxAvailableSize indicates the breakpoint to use this specification.
- * @param specType indicates whether the paddings and gutters will be applied vertically or
+ * @param dimensionType indicates whether the paddings and gutters will be applied vertically or
  *   horizontally.
+ * @param specType a [ResponsiveSpecType] that indicates the type of the spec.
  * @param startPadding padding used at the top or left (right in RTL) in the workspace folder.
  * @param endPadding padding used at the bottom or right (left in RTL) in the workspace folder.
- * @param gutter the space between the cells vertically or horizontally depending on the [specType].
- * @param cellSize height or width of the cell depending on the [specType].
+ * @param gutter the space between the cells vertically or horizontally depending on the
+ *   [dimensionType].
+ * @param cellSize height or width of the cell depending on the [dimensionType].
  */
-abstract class ResponsiveSpec(
-    open val maxAvailableSize: Int,
-    open val specType: SpecType,
-    open val startPadding: SizeSpec,
-    open val endPadding: SizeSpec,
-    open val gutter: SizeSpec,
-    open val cellSize: SizeSpec
-) {
-    open fun isValid(): Boolean {
+data class ResponsiveSpec(
+    override val maxAvailableSize: Int,
+    override val dimensionType: DimensionType,
+    override val specType: ResponsiveSpecType,
+    val startPadding: SizeSpec,
+    val endPadding: SizeSpec,
+    val gutter: SizeSpec,
+    val cellSize: SizeSpec,
+) : IResponsiveSpec {
+    init {
+        check(isValid()) { "Invalid ResponsiveSpec found." }
+    }
+
+    constructor(
+        responsiveSpecType: ResponsiveSpecType,
+        attrs: TypedArray,
+        specs: Map<String, SizeSpec>
+    ) : this(
+        maxAvailableSize =
+            attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+        dimensionType =
+            DimensionType.entries[
+                    attrs.getInt(
+                        R.styleable.ResponsiveSpec_specType,
+                        DimensionType.HEIGHT.ordinal
+                    )],
+        specType = responsiveSpecType,
+        startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
+        endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
+        gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
+        cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
+    )
+
+    fun isValid(): Boolean {
+        if (
+            (specType == ResponsiveSpecType.Workspace) &&
+                (startPadding.matchWorkspace ||
+                    endPadding.matchWorkspace ||
+                    gutter.matchWorkspace ||
+                    cellSize.matchWorkspace)
+        ) {
+            Log.e(LOG_TAG, "WorkspaceSpec#isValid - workspace shouldn't contain matchWorkspace!")
+            return false
+        }
+
         if (maxAvailableSize <= 0) {
             Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
             return false
@@ -106,13 +115,20 @@
             cellSize.isValid()
     }
 
-    enum class SpecType {
+    enum class DimensionType {
         HEIGHT,
         WIDTH
     }
 
     companion object {
         private const val LOG_TAG = "ResponsiveSpec"
+
+        enum class ResponsiveSpecType(val xmlTag: String) {
+            AllApps("allAppsSpec"),
+            Folder("folderSpec"),
+            Workspace("workspaceSpec"),
+            Hotseat("hotseatSpec")
+        }
     }
 }
 
@@ -120,7 +136,7 @@
  * Calculated responsive specs contains the final paddings, gutter and cell size in pixels after
  * they are calculated from the available space, cells and workspace specs.
  */
-sealed class CalculatedResponsiveSpec {
+class CalculatedResponsiveSpec {
     var availableSpace: Int = 0
         private set
 
@@ -146,7 +162,7 @@
         availableSpace: Int,
         cells: Int,
         spec: ResponsiveSpec,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+        calculatedWorkspaceSpec: CalculatedResponsiveSpec
     ) {
         this.availableSpace = availableSpace
         this.cells = cells
@@ -181,6 +197,8 @@
         updateRemainderSpaces(availableSpace, cells, spec)
     }
 
+    fun isResponsiveSpecType(type: ResponsiveSpecType) = spec.specType == type
+
     private fun updateRemainderSpaces(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
         val gutters = cells - 1
         val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
@@ -215,10 +233,10 @@
     }
 
     override fun toString(): String {
-        return "${this::class.simpleName}(" +
+        return "Calculated${spec.specType}Spec(" +
             "availableSpace=$availableSpace, cells=$cells, startPaddingPx=$startPaddingPx, " +
             "endPaddingPx=$endPaddingPx, gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
-            "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+            "${spec.specType}Spec.maxAvailableSize=${spec.maxAvailableSize}" +
             ")"
     }
 }
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt b/src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt
new file mode 100644
index 0000000..b233d7c
--- /dev/null
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecGroup.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.responsive
+
+import android.content.res.TypedArray
+import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
+
+/**
+ * Base class for responsive specs that holds a list of width and height specs.
+ *
+ * @param widthSpecs List of width responsive specifications
+ * @param heightSpecs List of height responsive specifications
+ */
+class ResponsiveSpecGroup<T : IResponsiveSpec>(
+    val aspectRatio: Float,
+    widthSpecs: List<T>,
+    heightSpecs: List<T>
+) {
+    val widthSpecs: List<T>
+    val heightSpecs: List<T>
+
+    init {
+        check(aspectRatio > 0f) { "Invalid aspect ratio! Aspect ratio should be bigger than zero." }
+        this.widthSpecs = widthSpecs.sortedBy { it.maxAvailableSize }
+        this.heightSpecs = heightSpecs.sortedBy { it.maxAvailableSize }
+    }
+
+    /**
+     * Get a [ResponsiveSpec] within the breakpoint.
+     *
+     * @param type Type of the spec to be retrieved (width or height)
+     * @param availableSize The breakpoint for the spec
+     * @return A [ResponsiveSpec].
+     */
+    fun getSpec(type: DimensionType, availableSize: Int): T {
+        val spec =
+            if (type == DimensionType.WIDTH) {
+                widthSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
+            } else {
+                heightSpecs.firstOrNull { availableSize <= it.maxAvailableSize }
+            }
+        check(spec != null) { "No available $type spec found within $availableSize." }
+        return spec
+    }
+
+    companion object {
+        const val XML_GROUP_NAME = "specs"
+
+        fun <T : IResponsiveSpec> create(
+            attrs: TypedArray,
+            specs: List<T>
+        ): ResponsiveSpecGroup<T> {
+            val (widthSpecs, heightSpecs) =
+                specs.partition { it.dimensionType == DimensionType.WIDTH }
+            val aspectRatio = attrs.getFloat(R.styleable.ResponsiveSpecGroup_maxAspectRatio, 0f)
+            return ResponsiveSpecGroup(aspectRatio, widthSpecs, heightSpecs)
+        }
+    }
+}
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt b/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt
index a89b619..d782cd5 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt
@@ -20,6 +20,7 @@
 import android.content.res.XmlResourceParser
 import android.util.Xml
 import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 import com.android.launcher3.util.ResourceHelper
 import java.io.IOException
 import org.xmlpull.v1.XmlPullParser
@@ -27,6 +28,80 @@
 
 class ResponsiveSpecsParser(private val resourceHelper: ResourceHelper) {
 
+    fun <T : IResponsiveSpec> parseXML(
+        responsiveSpecType: ResponsiveSpecType,
+        map:
+            (
+                responsiveSpecType: ResponsiveSpecType,
+                attributes: TypedArray,
+                sizeSpecs: Map<String, SizeSpec>
+            ) -> T
+    ): List<ResponsiveSpecGroup<T>> {
+        val parser: XmlResourceParser = resourceHelper.getXml()
+
+        try {
+            val groups = mutableListOf<ResponsiveSpecGroup<T>>()
+            val specs = mutableListOf<T>()
+            var groupAttrs: TypedArray? = null
+
+            var eventType = parser.eventType
+            while (eventType != XmlPullParser.END_DOCUMENT) {
+                // Parsing Group
+                when {
+                    parser starts ResponsiveSpecGroup.XML_GROUP_NAME -> {
+                        groupAttrs =
+                            resourceHelper.obtainStyledAttributes(
+                                Xml.asAttributeSet(parser),
+                                R.styleable.ResponsiveSpecGroup
+                            )
+                    }
+                    parser ends ResponsiveSpecGroup.XML_GROUP_NAME -> {
+                        checkNotNull(groupAttrs)
+                        groups += ResponsiveSpecGroup.create(groupAttrs, specs)
+                        specs.clear()
+                        groupAttrs.recycle()
+                        groupAttrs = null
+                    }
+                    // Mapping Spec to WorkspaceSpec, AllAppsSpec, FolderSpecs, HotseatSpec
+                    parser starts responsiveSpecType.xmlTag -> {
+                        val attrs =
+                            resourceHelper.obtainStyledAttributes(
+                                Xml.asAttributeSet(parser),
+                                R.styleable.ResponsiveSpec
+                            )
+
+                        val sizeSpecs = parseSizeSpecs(parser)
+                        specs += map(responsiveSpecType, attrs, sizeSpecs)
+                        attrs.recycle()
+                    }
+                }
+
+                eventType = parser.next()
+            }
+
+            parser.close()
+
+            // All the specs should have been linked to a group, otherwise the XML is invalid
+            check(specs.isEmpty()) {
+                throw InvalidResponsiveGridSpec(
+                    "Invalid XML. ${specs.size} specs not linked to a group."
+                )
+            }
+
+            return groups
+        } catch (e: Exception) {
+            when (e) {
+                is NoSuchFieldException,
+                is IOException,
+                is XmlPullParserException ->
+                    throw RuntimeException("Failure parsing specs file.", e)
+                else -> throw e
+            }
+        } finally {
+            parser.close()
+        }
+    }
+
     private fun parseSizeSpecs(parser: XmlResourceParser): Map<String, SizeSpec> {
         val parentName = parser.name
         parser.next()
@@ -42,49 +117,15 @@
         return result
     }
 
-    fun <T> parseXML(
-        tagName: String,
-        map: (attributes: TypedArray, sizeSpecs: Map<String, SizeSpec>) -> T
-    ): List<T> {
-        val parser: XmlResourceParser = resourceHelper.getXml()
+    private infix fun XmlResourceParser.starts(tag: String): Boolean =
+        name == tag && eventType == XmlPullParser.START_TAG
 
-        try {
-            val list = mutableListOf<T>()
-
-            var eventType = parser.eventType
-            while (eventType != XmlPullParser.END_DOCUMENT) {
-                if (eventType == XmlResourceParser.START_TAG && parser.name == tagName) {
-                    val attrs =
-                        resourceHelper.obtainStyledAttributes(
-                            Xml.asAttributeSet(parser),
-                            R.styleable.ResponsiveSpec
-                        )
-
-                    val sizeSpecs = parseSizeSpecs(parser)
-                    list += map(attrs, sizeSpecs)
-                    attrs.recycle()
-                }
-
-                eventType = parser.next()
-            }
-
-            parser.close()
-
-            return list
-        } catch (e: Exception) {
-            when (e) {
-                is NoSuchFieldException,
-                is IOException,
-                is XmlPullParserException ->
-                    throw RuntimeException("Failure parsing specs file.", e)
-                else -> throw e
-            }
-        } finally {
-            parser.close()
-        }
-    }
+    private infix fun XmlResourceParser.ends(tag: String): Boolean =
+        name == tag && eventType == XmlPullParser.END_TAG
 }
 
 fun Map<String, SizeSpec>.getOrError(key: String): SizeSpec {
     return this.getOrElse(key) { error("Attr '$key' must be defined.") }
 }
+
+class InvalidResponsiveGridSpec(message: String) : Exception(message)
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt b/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
new file mode 100644
index 0000000..bc2f4c5
--- /dev/null
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.responsive
+
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
+import com.android.launcher3.util.ResourceHelper
+
+/**
+ * A class to provide responsive grid specs for workspace, folder and all apps.
+ *
+ * This class is responsible for provide width and height [CalculatedResponsiveSpec] to be used for
+ * the correct placement of the workspace, all apps and folders.
+ *
+ * @param type A [ResponsiveSpecType] to indicates the type of the spec.
+ * @param groupOfSpecs Groups of responsive specifications
+ */
+class ResponsiveSpecsProvider(
+    val type: ResponsiveSpecType,
+    groupOfSpecs: List<ResponsiveSpecGroup<ResponsiveSpec>>
+) {
+    private val groupOfSpecs: List<ResponsiveSpecGroup<ResponsiveSpec>>
+
+    init {
+        this.groupOfSpecs =
+            groupOfSpecs
+                .onEach { group ->
+                    check(group.widthSpecs.isNotEmpty() && group.heightSpecs.isNotEmpty()) {
+                        "${this::class.simpleName} is incomplete - " +
+                            "width list size = ${group.widthSpecs.size}; " +
+                            "height list size = ${group.heightSpecs.size}."
+                    }
+                }
+                .sortedBy { it.aspectRatio }
+    }
+
+    fun getSpecsByAspectRatio(aspectRatio: Float): ResponsiveSpecGroup<ResponsiveSpec> {
+        check(aspectRatio > 0f) { "Invalid aspect ratio! The value should be bigger than 0." }
+
+        val specsGroup = groupOfSpecs.firstOrNull { aspectRatio <= it.aspectRatio }
+        checkNotNull(specsGroup) { "No available spec with aspectRatio within $aspectRatio." }
+
+        return specsGroup
+    }
+
+    /**
+     * Retrieves a responsive grid specification that matches the number of [numCells],
+     * * [availableSpace] and [aspectRatio].
+     *
+     * @param aspectRatio the device width divided by device height (aspect ratio) to filter the
+     *   specifications
+     * @param dimensionType the grid axis of the spec width is x axis, height is y axis.
+     * @param numCells number of rows/columns in the grid
+     * @param availableSpace available width to filter the specifications
+     * @return A [CalculatedResponsiveSpec] that matches the parameters provided.
+     */
+    fun getCalculatedSpec(
+        aspectRatio: Float,
+        dimensionType: DimensionType,
+        numCells: Int,
+        availableSpace: Int,
+    ): CalculatedResponsiveSpec {
+        val specsGroup = getSpecsByAspectRatio(aspectRatio)
+        val spec = specsGroup.getSpec(dimensionType, availableSpace)
+        return CalculatedResponsiveSpec(availableSpace, numCells, spec)
+    }
+
+    /**
+     * Retrieves a responsive grid specification that matches the number of [numCells],
+     * * [availableSpace] and [aspectRatio]. This function uses a [CalculatedResponsiveSpec] to
+     *   match workspace when its true.
+     *
+     * @param aspectRatio the device width divided by device height (aspect ratio) to filter the
+     *   specifications
+     * @param dimensionType the grid axis of the spec width is x axis, height is y axis.
+     * @param numCells number of rows/columns in the grid
+     * @param availableSpace available width to filter the specifications
+     * @param calculatedWorkspaceSpec the calculated workspace specification to use its values as
+     *   base when matchWorkspace is true.
+     * @return A [CalculatedResponsiveSpec] that matches the parameters provided.
+     */
+    fun getCalculatedSpec(
+        aspectRatio: Float,
+        dimensionType: DimensionType,
+        numCells: Int,
+        availableSpace: Int,
+        calculatedWorkspaceSpec: CalculatedResponsiveSpec
+    ): CalculatedResponsiveSpec {
+        check(calculatedWorkspaceSpec.spec.dimensionType == dimensionType) {
+            "Invalid specType for CalculatedWorkspaceSpec. " +
+                "Expected: $dimensionType - " +
+                "Found: ${calculatedWorkspaceSpec.spec.dimensionType}}"
+        }
+
+        check(calculatedWorkspaceSpec.isResponsiveSpecType(ResponsiveSpecType.Workspace)) {
+            "Invalid specType for CalculatedWorkspaceSpec. " +
+                "Expected: ${ResponsiveSpecType.Workspace} - " +
+                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
+        }
+
+        val specsGroup = getSpecsByAspectRatio(aspectRatio)
+        val spec = specsGroup.getSpec(dimensionType, availableSpace)
+        return CalculatedResponsiveSpec(availableSpace, numCells, spec, calculatedWorkspaceSpec)
+    }
+
+    companion object {
+        @JvmStatic
+        fun create(
+            resourceHelper: ResourceHelper,
+            type: ResponsiveSpecType
+        ): ResponsiveSpecsProvider {
+            val parser = ResponsiveSpecsParser(resourceHelper)
+            val specs = parser.parseXML(type, ::ResponsiveSpec)
+            return ResponsiveSpecsProvider(type, specs)
+        }
+    }
+}
diff --git a/src/com/android/launcher3/responsive/WorkspaceSpecs.kt b/src/com/android/launcher3/responsive/WorkspaceSpecs.kt
deleted file mode 100644
index 0da7026..0000000
--- a/src/com/android/launcher3/responsive/WorkspaceSpecs.kt
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.responsive
-
-import android.content.res.TypedArray
-import android.util.Log
-import com.android.launcher3.R
-import com.android.launcher3.responsive.ResponsiveSpec.SpecType
-import com.android.launcher3.util.ResourceHelper
-
-private const val TAG = "WorkspaceSpecs"
-
-class WorkspaceSpecs(widthSpecs: List<WorkspaceSpec>, heightSpecs: List<WorkspaceSpec>) :
-    ResponsiveSpecs<WorkspaceSpec>(widthSpecs, heightSpecs) {
-
-    fun getCalculatedWidthSpec(columns: Int, availableWidth: Int): CalculatedWorkspaceSpec {
-        val spec = getWidthSpec(availableWidth)
-        return CalculatedWorkspaceSpec(availableWidth, columns, spec)
-    }
-
-    fun getCalculatedHeightSpec(rows: Int, availableHeight: Int): CalculatedWorkspaceSpec {
-        val spec = getHeightSpec(availableHeight)
-        return CalculatedWorkspaceSpec(availableHeight, rows, spec)
-    }
-
-    companion object {
-        private const val XML_WORKSPACE_SPEC = "workspaceSpec"
-
-        @JvmStatic
-        fun create(resourceHelper: ResourceHelper): WorkspaceSpecs {
-            val parser = ResponsiveSpecsParser(resourceHelper)
-            val specs = parser.parseXML(XML_WORKSPACE_SPEC, ::WorkspaceSpec)
-            val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
-            return WorkspaceSpecs(widthSpecs, heightSpecs)
-        }
-    }
-}
-
-data class WorkspaceSpec(
-    override val maxAvailableSize: Int,
-    override val specType: SpecType,
-    override val startPadding: SizeSpec,
-    override val endPadding: SizeSpec,
-    override val gutter: SizeSpec,
-    override val cellSize: SizeSpec
-) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
-
-    init {
-        check(isValid()) { "Invalid WorkspaceSpec found." }
-    }
-
-    constructor(
-        attrs: TypedArray,
-        specs: Map<String, SizeSpec>
-    ) : this(
-        maxAvailableSize =
-            attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
-        specType =
-            SpecType.values()[
-                    attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
-        startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
-        endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
-        gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
-        cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
-    )
-
-    override fun isValid(): Boolean {
-        // Workspace spec should not match workspace
-        if (
-            startPadding.matchWorkspace ||
-                endPadding.matchWorkspace ||
-                gutter.matchWorkspace ||
-                cellSize.matchWorkspace
-        ) {
-            Log.e(TAG, "WorkspaceSpec#isValid - workspace shouldn't contain matchWorkspace!")
-            return false
-        }
-
-        return super.isValid()
-    }
-}
-
-class CalculatedWorkspaceSpec(availableSpace: Int, cells: Int, spec: WorkspaceSpec) :
-    CalculatedResponsiveSpec(availableSpace, cells, spec)
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 228af4c..650fbcc 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -18,7 +18,6 @@
 
 import static android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED;
 
-import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
 import static androidx.preference.PreferenceFragmentCompat.ARG_PREFERENCE_ROOT;
 
 import static com.android.launcher3.BuildConfig.IS_DEBUG_DEVICE;
@@ -34,7 +33,6 @@
 import android.view.MenuItem;
 import android.view.View;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.view.WindowCompat;
@@ -290,8 +288,6 @@
                 if (highlighter != null) {
                     getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
                     mPreferenceHighlighted = true;
-                } else {
-                    requestAccessibilityFocus(getListView());
                 }
             }
 
@@ -350,14 +346,5 @@
                     list, position, screen.findPreference(mHighLightKey))
                     : null;
         }
-
-        private void requestAccessibilityFocus(@NonNull final RecyclerView rv) {
-            rv.post(() -> {
-                if (!rv.hasFocus() && rv.getChildCount() > 0) {
-                    rv.getChildAt(0)
-                            .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
-                }
-            });
-        }
     }
 }
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index 70691f8..60d0e95 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -76,7 +76,7 @@
             // be more useful.
             // That's why we pass false as the value for the 'reportToTapl' parameter.
             recordEventSlow(sequence, message + ": " + event, false);
-            registerEventNotFromTest(event);
+            if (action != MotionEvent.ACTION_CANCEL) registerEventNotFromTest(event);
         }
     }
 
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 8c12547..db32829 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -15,8 +15,8 @@
  */
 package com.android.launcher3.touch;
 
-import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
-import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index cf59085..cbc85b6 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -149,6 +149,13 @@
     public void assignMarginsAndBackgrounds(ViewGroup viewGroup) {
         assignMarginsAndBackgrounds(viewGroup,
                 getColorStateList(getContext(), mColorIds[0]).getDefaultColor());
+        // last shortcut doesn't need bottom margin
+        final int count = viewGroup.getChildCount() - 1;
+        for (int i = 0; i < count; i++) {
+            // These are shortcuts and not shortcut containers, but they still need bottom margin
+            MarginLayoutParams mlp = (MarginLayoutParams) viewGroup.getChildAt(i).getLayoutParams();
+            mlp.bottomMargin = mChildContainerMargin;
+        }
     }
 
     public static <T extends Context & ActivityContext> OptionsPopupView<T> show(
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index 6f74fd9..99485be 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -257,8 +257,6 @@
             throw new RuntimeException("Max size is too small for preview");
         }
         return BitmapRenderer.createHardwareBitmap(size, size, c -> {
-            drawBoxWithShadow(c, size, size);
-
             LauncherIcons li = LauncherIcons.obtain(mContext);
             Drawable icon = li.createBadgedIconBitmap(
                     mutateOnMainThread(info.getFullResIcon(
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 78116ae..af77d03 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -101,6 +101,7 @@
     // the table can display.
     private static final float RECOMMENDATION_TABLE_HEIGHT_RATIO = 0.75f;
 
+    private final UserCache mUserCache;
     private final UserManagerState mUserManagerState = new UserManagerState();
     private final UserHandle mCurrentUser = Process.myUserHandle();
     private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter =
@@ -192,6 +193,7 @@
                 ? resources.getDimensionPixelSize(R.dimen.all_apps_header_pill_height)
                 : 0;
 
+        mUserCache = UserCache.INSTANCE.get(context);
         mUserManagerState.init(UserCache.INSTANCE.get(context),
                 context.getSystemService(UserManager.class));
     }
@@ -311,7 +313,9 @@
         if (adapterHolder.mAdapterType == AdapterHolder.SEARCH) {
             mNoWidgetsView.setText(R.string.no_search_results);
         } else if (adapterHolder.mAdapterType == AdapterHolder.WORK
-                && mUserManagerState.isAnyProfileQuietModeEnabled()
+                && mUserCache.getUserProfiles().stream()
+                .filter(userHandle -> mUserCache.getUserInfo(userHandle).isWork())
+                .anyMatch(mUserManagerState::isUserQuiet)
                 && mActivityContext.getStringCache() != null) {
             mNoWidgetsView.setText(mActivityContext.getStringCache().workProfilePausedTitle);
         } else {
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index ee151bb..bd9da0a 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -42,18 +42,7 @@
         </receiver>
 
         <receiver
-            android:name="com.android.launcher3.testcomponent.AppWidgetNoConfigLarge"
-            android:exported="true"
-            android:label="No Config Large">
-            <intent-filter>
-                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
-            </intent-filter>
-            <meta-data android:name="android.appwidget.provider"
-                android:resource="@xml/appwidget_no_config_large"/>
-        </receiver>
-
-        <receiver
-            android:name="com.android.launcher3.testcomponent.AppWdigetHidden"
+            android:name="com.android.launcher3.testcomponent.AppWidgetHidden"
             android:exported="true"
             android:label="Hidden widget">
             <intent-filter>
@@ -84,7 +73,7 @@
                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
             </intent-filter>
             <meta-data android:name="android.appwidget.provider"
-                android:resource="@xml/appwidget_no_config"/>
+                android:resource="@xml/appwidget_no_config_large"/>
         </receiver>
 
         <receiver
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
index 3fff622..0aed1e1 100644
--- a/tests/Launcher3Tests.xml
+++ b/tests/Launcher3Tests.xml
@@ -46,5 +46,6 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.launcher3.tests" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="instrumentation-arg" key="waitForActivitiesToComplete" value="false" />
     </test>
 </configuration>
diff --git a/tests/res/values/attrs.xml b/tests/res/values/attrs.xml
index e5ee064..4ba4fc7 100644
--- a/tests/res/values/attrs.xml
+++ b/tests/res/values/attrs.xml
@@ -26,6 +26,10 @@
         <attr name="maxAvailableSize" format="dimension" />
     </declare-styleable>
 
+    <declare-styleable name="ResponsiveSpecGroup">
+        <attr name="maxAspectRatio" format="float" />
+    </declare-styleable>
+
     <declare-styleable name="WorkspaceSpec">
         <attr name="specType" />
         <attr name="maxAvailableSize" />
diff --git a/tests/res/xml/invalid_all_apps_file_case_1.xml b/tests/res/xml/invalid_all_apps_file_case_1.xml
index 6fd35b1..f09ecd8 100644
--- a/tests/res/xml/invalid_all_apps_file_case_1.xml
+++ b/tests/res/xml/invalid_all_apps_file_case_1.xml
@@ -14,23 +14,24 @@
   ~ limitations under the License.
   -->
 <allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <allAppsSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <!--  missing startPadding  -->
-        <endPadding launcher:fixedSize="0dp" />
-        <gutter launcher:matchWorkspace="true" />
-        <cellSize launcher:matchWorkspace="true" />
-    </allAppsSpec>
+    <specs launcher:maxAspectRatio="10">
+        <allAppsSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <!--  missing startPadding  -->
+            <endPadding launcher:fixedSize="0dp" />
+            <gutter launcher:matchWorkspace="true" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
 
-    <allAppsSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:matchWorkspace="true" />
-        <endPadding launcher:matchWorkspace="true" />
-        <gutter launcher:matchWorkspace="true" />
-        <cellSize launcher:matchWorkspace="true" />
-    </allAppsSpec>
-
+        <allAppsSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:matchWorkspace="true" />
+            <endPadding launcher:matchWorkspace="true" />
+            <gutter launcher:matchWorkspace="true" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
+    </specs>
 </allAppsSpecs>
 
diff --git a/tests/res/xml/invalid_all_apps_file_case_2.xml b/tests/res/xml/invalid_all_apps_file_case_2.xml
index de9c1ac..7644def 100644
--- a/tests/res/xml/invalid_all_apps_file_case_2.xml
+++ b/tests/res/xml/invalid_all_apps_file_case_2.xml
@@ -14,25 +14,26 @@
   ~ limitations under the License.
   -->
 <allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <allAppsSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="0dp" />
-        <endPadding launcher:fixedSize="0dp" />
-        <!--  more than 1 value in one tag -->
-        <gutter
-            launcher:matchWorkspace="true"
-            launcher:fixedSize="16dp" />
-        <cellSize launcher:matchWorkspace="true" />
-    </allAppsSpec>
+    <specs launcher:maxAspectRatio="10">
+        <allAppsSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="0dp" />
+            <!--  more than 1 value in one tag -->
+            <gutter
+                launcher:matchWorkspace="true"
+                launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
 
-    <allAppsSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:matchWorkspace="true" />
-        <endPadding launcher:matchWorkspace="true" />
-        <gutter launcher:matchWorkspace="true" />
-        <cellSize launcher:matchWorkspace="true" />
-    </allAppsSpec>
-
+        <allAppsSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:matchWorkspace="true" />
+            <endPadding launcher:matchWorkspace="true" />
+            <gutter launcher:matchWorkspace="true" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
+    </specs>
 </allAppsSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_all_apps_file_case_3.xml b/tests/res/xml/invalid_all_apps_file_case_3.xml
index 7af0af4..5a3bc64 100644
--- a/tests/res/xml/invalid_all_apps_file_case_3.xml
+++ b/tests/res/xml/invalid_all_apps_file_case_3.xml
@@ -14,24 +14,25 @@
   ~ limitations under the License.
   -->
 <allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <allAppsSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="0dp" />
-        <endPadding launcher:fixedSize="0dp" />
-        <gutter launcher:matchWorkspace="true" />
-        <!--  value bigger than 1 -->
-        <cellSize launcher:ofRemainderSpace="1.001" />
-    </allAppsSpec>
+    <specs launcher:maxAspectRatio="10">
+        <allAppsSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="0dp" />
+            <gutter launcher:matchWorkspace="true" />
+            <!--  value bigger than 1 -->
+            <cellSize launcher:ofRemainderSpace="1.001" />
+        </allAppsSpec>
 
-    <allAppsSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:matchWorkspace="true" />
-        <endPadding launcher:matchWorkspace="true" />
-        <gutter launcher:matchWorkspace="true" />
-        <cellSize launcher:matchWorkspace="true" />
-    </allAppsSpec>
-
+        <allAppsSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:matchWorkspace="true" />
+            <endPadding launcher:matchWorkspace="true" />
+            <gutter launcher:matchWorkspace="true" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
+    </specs>
 </allAppsSpecs>
 
diff --git a/tests/res/xml/invalid_folders_specs_1.xml b/tests/res/xml/invalid_folders_specs_1.xml
index 0864249..1715ffd 100644
--- a/tests/res/xml/invalid_folders_specs_1.xml
+++ b/tests/res/xml/invalid_folders_specs_1.xml
@@ -16,25 +16,27 @@
 
 <!-- Tablet - 6x5 portrait -->
 <folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
-        <!--  missing startPadding  -->
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:matchWorkspace="true" />
-    </folderSpec>
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="102dp" />
-    </folderSpec>
+    <specs launcher:maxAspectRatio="10">
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+            <!--  missing startPadding  -->
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </folderSpec>
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="102dp" />
+        </folderSpec>
 
-    <!-- Height spec is fixed -->
-    <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="24dp" />
-        <!-- mapped to footer height size -->
-        <endPadding launcher:fixedSize="64dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="104dp" />
-    </folderSpec>
+        <!-- Height spec is fixed -->
+        <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="24dp" />
+            <!-- mapped to footer height size -->
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </folderSpec>
+    </specs>
 </folderSpecs>
diff --git a/tests/res/xml/invalid_folders_specs_2.xml b/tests/res/xml/invalid_folders_specs_2.xml
index 0b7dd62..bb26e2e 100644
--- a/tests/res/xml/invalid_folders_specs_2.xml
+++ b/tests/res/xml/invalid_folders_specs_2.xml
@@ -16,28 +16,30 @@
 
 <!-- Tablet - 6x5 portrait -->
 <folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <!--  more than 1 value in one tag -->
-        <gutter
-            launcher:ofAvailableSpace="0.0125"
-            launcher:fixedSize="16dp" />
-        <cellSize launcher:matchWorkspace="true" />
-    </folderSpec>
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="102dp" />
-    </folderSpec>
+    <specs launcher:maxAspectRatio="10">
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <!--  more than 1 value in one tag -->
+            <gutter
+                launcher:ofAvailableSpace="0.0125"
+                launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </folderSpec>
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="102dp" />
+        </folderSpec>
 
-    <!-- Height spec is fixed -->
-    <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="24dp" />
-        <!-- mapped to footer height size -->
-        <endPadding launcher:fixedSize="64dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="104dp" />
-    </folderSpec>
+        <!-- Height spec is fixed -->
+        <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="24dp" />
+            <!-- mapped to footer height size -->
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </folderSpec>
+    </specs>
 </folderSpecs>
diff --git a/tests/res/xml/invalid_folders_specs_3.xml b/tests/res/xml/invalid_folders_specs_3.xml
index 83fd3e1..cdd8056 100644
--- a/tests/res/xml/invalid_folders_specs_3.xml
+++ b/tests/res/xml/invalid_folders_specs_3.xml
@@ -16,26 +16,28 @@
 
 <!-- Tablet - 6x5 portrait - More the one value first gutter -->
 <folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <!--  value bigger than 1 -->
-        <cellSize launcher:ofRemainderSpace="1.001" />
-    </folderSpec>
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="102dp" />
-    </folderSpec>
+    <specs launcher:maxAspectRatio="10">
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <!--  value bigger than 1 -->
+            <cellSize launcher:ofRemainderSpace="1.001" />
+        </folderSpec>
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="102dp" />
+        </folderSpec>
 
-    <!-- Height spec is fixed -->
-    <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="24dp" />
-        <!-- mapped to footer height size -->
-        <endPadding launcher:fixedSize="64dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="104dp" />
-    </folderSpec>
+        <!-- Height spec is fixed -->
+        <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="24dp" />
+            <!-- mapped to footer height size -->
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </folderSpec>
+    </specs>
 </folderSpecs>
diff --git a/tests/res/xml/invalid_folders_specs_4.xml b/tests/res/xml/invalid_folders_specs_4.xml
index 2d8c730..cd5f334 100644
--- a/tests/res/xml/invalid_folders_specs_4.xml
+++ b/tests/res/xml/invalid_folders_specs_4.xml
@@ -15,10 +15,12 @@
   -->
 <!-- missing height spec -->
 <folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:matchWorkspace="true" />
-    </folderSpec>
+    <specs launcher:maxAspectRatio="10">
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </folderSpec>
+    </specs>
 </folderSpecs>
diff --git a/tests/res/xml/invalid_folders_specs_5.xml b/tests/res/xml/invalid_folders_specs_5.xml
index b4f1f4d..f554521 100644
--- a/tests/res/xml/invalid_folders_specs_5.xml
+++ b/tests/res/xml/invalid_folders_specs_5.xml
@@ -15,19 +15,21 @@
   -->
 <!-- missing breakpoints > 800dp -->
 <folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:matchWorkspace="true" />
-    </folderSpec>
+    <specs launcher:maxAspectRatio="10">
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </folderSpec>
 
-    <!-- Height spec is fixed -->
-    <folderSpec launcher:specType="height" launcher:maxAvailableSize="800dp">
-        <startPadding launcher:fixedSize="24dp" />
-        <!-- mapped to footer height size -->
-        <endPadding launcher:fixedSize="64dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="104dp" />
-    </folderSpec>
+        <!-- Height spec is fixed -->
+        <folderSpec launcher:specType="height" launcher:maxAvailableSize="800dp">
+            <startPadding launcher:fixedSize="24dp" />
+            <!-- mapped to footer height size -->
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </folderSpec>
+    </specs>
 </folderSpecs>
diff --git a/tests/res/xml/invalid_hotseat_file_case_1.xml b/tests/res/xml/invalid_hotseat_file_case_1.xml
index aaaf8bb..bd40690 100644
--- a/tests/res/xml/invalid_hotseat_file_case_1.xml
+++ b/tests/res/xml/invalid_hotseat_file_case_1.xml
@@ -14,19 +14,19 @@
   ~ limitations under the License.
   -->
 <hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <hotseatSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="847dp">
+            <hotseatQsbSpace launcher:ofAvailableSpace="1" />
+            <edgePadding launcher:fixedSize="36dp" />
+        </hotseatSpec>
 
-    <hotseatSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="847dp">
-        <hotseatQsbSpace launcher:ofAvailableSpace="1" />
-        <edgePadding launcher:fixedSize="36dp" />
-    </hotseatSpec>
-
-    <hotseatSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <hotseatQsbSpace launcher:fixedSize="36dp" />
-        <edgePadding launcher:fixedSize="36dp" />
-    </hotseatSpec>
-
+        <hotseatSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <hotseatQsbSpace launcher:fixedSize="36dp" />
+            <edgePadding launcher:fixedSize="36dp" />
+        </hotseatSpec>
+    </specs>
 </hotseatSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_responsive_spec_1.xml b/tests/res/xml/invalid_responsive_spec_1.xml
new file mode 100644
index 0000000..7a474bf
--- /dev/null
+++ b/tests/res/xml/invalid_responsive_spec_1.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+<!-- invalid file - missing groups (specs) with aspect ratio -->
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <workspaceSpec
+        launcher:maxAvailableSize="9999dp"
+        launcher:specType="height">
+        <cellSize launcher:fixedSize="104dp" />
+        <endPadding launcher:ofRemainderSpace="1" />
+        <gutter launcher:fixedSize="16dp" />
+        <startPadding launcher:fixedSize="8dp" />
+    </workspaceSpec>
+
+    <workspaceSpec
+        launcher:maxAvailableSize="9999dp"
+        launcher:specType="width">
+        <cellSize launcher:ofRemainderSpace="0.25" />
+        <endPadding launcher:fixedSize="22dp" />
+        <gutter launcher:fixedSize="16dp" />
+        <startPadding launcher:fixedSize="22dp" />
+    </workspaceSpec>
+</workspaceSpecs>
diff --git a/tests/res/xml/invalid_responsive_spec_2.xml b/tests/res/xml/invalid_responsive_spec_2.xml
new file mode 100644
index 0000000..6735300
--- /dev/null
+++ b/tests/res/xml/invalid_responsive_spec_2.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+<!-- invalid file - groups (specs) applied only for half of the specs  -->
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:specType="height">
+            <cellSize launcher:fixedSize="104dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="8dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:specType="width">
+            <cellSize launcher:ofRemainderSpace="0.25" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="22dp" />
+        </workspaceSpec>
+    </specs>
+
+    <workspaceSpec
+        launcher:maxAvailableSize="9999dp"
+        launcher:specType="width">
+        <cellSize launcher:ofRemainderSpace="0.25" />
+        <endPadding launcher:fixedSize="22dp" />
+        <gutter launcher:fixedSize="16dp" />
+        <startPadding launcher:fixedSize="22dp" />
+    </workspaceSpec>
+    <workspaceSpec
+        launcher:maxAvailableSize="9999dp"
+        launcher:specType="height">
+        <cellSize launcher:fixedSize="104dp" />
+        <endPadding launcher:ofRemainderSpace="1" />
+        <gutter launcher:fixedSize="16dp" />
+        <startPadding launcher:fixedSize="8dp" />
+    </workspaceSpec>
+</workspaceSpecs>
diff --git a/tests/res/xml/invalid_responsive_spec_3.xml b/tests/res/xml/invalid_responsive_spec_3.xml
new file mode 100644
index 0000000..72d0c20
--- /dev/null
+++ b/tests/res/xml/invalid_responsive_spec_3.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+<!-- invalid file - aspect ratio = 0 (it must be bigger than zero) -->
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:specType="height">
+            <cellSize launcher:fixedSize="104dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="8dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:specType="width">
+            <cellSize launcher:ofRemainderSpace="0.25" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="22dp" />
+        </workspaceSpec>
+    </specs>
+
+    <specs launcher:maxAspectRatio="0">
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:specType="height">
+            <cellSize launcher:fixedSize="104dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="8dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:specType="width">
+            <cellSize launcher:ofRemainderSpace="0.25" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="22dp" />
+        </workspaceSpec>
+    </specs>
+
+</workspaceSpecs>
diff --git a/tests/res/xml/invalid_workspace_file_case_1.xml b/tests/res/xml/invalid_workspace_file_case_1.xml
index 0be704b..fc6ff59 100644
--- a/tests/res/xml/invalid_workspace_file_case_1.xml
+++ b/tests/res/xml/invalid_workspace_file_case_1.xml
@@ -15,42 +15,44 @@
   -->
 
 <workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="648dp">
-        <!--  missing startPadding  -->
-        <endPadding
-            launcher:ofAvailableSpace="0.05" />
-        <gutter
-            launcher:fixedSize="16dp" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
-    </workspaceSpec>
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="648dp">
+            <!--  missing startPadding  -->
+            <endPadding
+                launcher:ofAvailableSpace="0.05" />
+            <gutter
+                launcher:fixedSize="16dp" />
+            <cellSize
+                launcher:ofRemainderSpace="0.2" />
+        </workspaceSpec>
 
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0306" />
-        <endPadding
-            launcher:ofAvailableSpace="0.068" />
-        <gutter
-            launcher:fixedSize="16dp" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
-    </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding
+                launcher:ofAvailableSpace="0.0306" />
+            <endPadding
+                launcher:ofAvailableSpace="0.068" />
+            <gutter
+                launcher:fixedSize="16dp" />
+            <cellSize
+                launcher:ofRemainderSpace="0.2" />
+        </workspaceSpec>
 
-    <!-- Width spec is always the same -->
-    <workspaceSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <endPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <gutter
-            launcher:ofRemainderSpace="0.11425509" />
-        <cellSize
-            launcher:fixedSize="120dp" />
-    </workspaceSpec>
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding
+                launcher:ofRemainderSpace="0.21436227" />
+            <endPadding
+                launcher:ofRemainderSpace="0.21436227" />
+            <gutter
+                launcher:ofRemainderSpace="0.11425509" />
+            <cellSize
+                launcher:fixedSize="120dp" />
+        </workspaceSpec>
+    </specs>
 </workspaceSpecs>
diff --git a/tests/res/xml/invalid_workspace_file_case_2.xml b/tests/res/xml/invalid_workspace_file_case_2.xml
index 5a37d97..2feeb54 100644
--- a/tests/res/xml/invalid_workspace_file_case_2.xml
+++ b/tests/res/xml/invalid_workspace_file_case_2.xml
@@ -15,45 +15,47 @@
   -->
 
 <workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="648dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0125" />
-        <endPadding
-            launcher:ofAvailableSpace="0.05" />
-        <!--  more than 1 value in one tag -->
-        <gutter
-            launcher:ofAvailableSpace="0.0125"
-            launcher:fixedSize="16dp" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
-    </workspaceSpec>
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="648dp">
+            <startPadding
+                launcher:ofAvailableSpace="0.0125" />
+            <endPadding
+                launcher:ofAvailableSpace="0.05" />
+            <!--  more than 1 value in one tag -->
+            <gutter
+                launcher:ofAvailableSpace="0.0125"
+                launcher:fixedSize="16dp" />
+            <cellSize
+                launcher:ofRemainderSpace="0.2" />
+        </workspaceSpec>
 
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0306" />
-        <endPadding
-            launcher:ofAvailableSpace="0.068" />
-        <gutter
-            launcher:fixedSize="16dp" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
-    </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding
+                launcher:ofAvailableSpace="0.0306" />
+            <endPadding
+                launcher:ofAvailableSpace="0.068" />
+            <gutter
+                launcher:fixedSize="16dp" />
+            <cellSize
+                launcher:ofRemainderSpace="0.2" />
+        </workspaceSpec>
 
-    <!-- Width spec is always the same -->
-    <workspaceSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <endPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <gutter
-            launcher:ofRemainderSpace="0.11425509" />
-        <cellSize
-            launcher:fixedSize="120dp" />
-    </workspaceSpec>
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding
+                launcher:ofRemainderSpace="0.21436227" />
+            <endPadding
+                launcher:ofRemainderSpace="0.21436227" />
+            <gutter
+                launcher:ofRemainderSpace="0.11425509" />
+            <cellSize
+                launcher:fixedSize="120dp" />
+        </workspaceSpec>
+    </specs>
 </workspaceSpecs>
diff --git a/tests/res/xml/invalid_workspace_file_case_3.xml b/tests/res/xml/invalid_workspace_file_case_3.xml
index 3e68edb..79e6fbd 100644
--- a/tests/res/xml/invalid_workspace_file_case_3.xml
+++ b/tests/res/xml/invalid_workspace_file_case_3.xml
@@ -15,44 +15,46 @@
   -->
 
 <workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="648dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0125" />
-        <endPadding
-            launcher:ofAvailableSpace="0.05" />
-        <gutter
-            launcher:fixedSize="16dp" />
-        <!--  value bigger than 1 -->
-        <cellSize
-            launcher:ofRemainderSpace="1.001" />
-    </workspaceSpec>
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="648dp">
+            <startPadding
+                launcher:ofAvailableSpace="0.0125" />
+            <endPadding
+                launcher:ofAvailableSpace="0.05" />
+            <gutter
+                launcher:fixedSize="16dp" />
+            <!--  value bigger than 1 -->
+            <cellSize
+                launcher:ofRemainderSpace="1.001" />
+        </workspaceSpec>
 
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0306" />
-        <endPadding
-            launcher:ofAvailableSpace="0.068" />
-        <gutter
-            launcher:fixedSize="16dp" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
-    </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding
+                launcher:ofAvailableSpace="0.0306" />
+            <endPadding
+                launcher:ofAvailableSpace="0.068" />
+            <gutter
+                launcher:fixedSize="16dp" />
+            <cellSize
+                launcher:ofRemainderSpace="0.2" />
+        </workspaceSpec>
 
-    <!-- Width spec is always the same -->
-    <workspaceSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <endPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <gutter
-            launcher:ofRemainderSpace="0.11425509" />
-        <cellSize
-            launcher:fixedSize="120dp" />
-    </workspaceSpec>
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding
+                launcher:ofRemainderSpace="0.21436227" />
+            <endPadding
+                launcher:ofRemainderSpace="0.21436227" />
+            <gutter
+                launcher:ofRemainderSpace="0.11425509" />
+            <cellSize
+                launcher:fixedSize="120dp" />
+        </workspaceSpec>
+    </specs>
 </workspaceSpecs>
diff --git a/tests/res/xml/invalid_workspace_file_case_4.xml b/tests/res/xml/invalid_workspace_file_case_4.xml
index 9e74c85..66a2618 100644
--- a/tests/res/xml/invalid_workspace_file_case_4.xml
+++ b/tests/res/xml/invalid_workspace_file_case_4.xml
@@ -15,44 +15,46 @@
   -->
 
 <workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="648dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0125" />
-        <endPadding
-            launcher:ofAvailableSpace="0.05" />
-        <!--  value in workspace spec using matchWorkspace -->
-        <gutter
-            launcher:matchWorkspace="true" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
-    </workspaceSpec>
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="648dp">
+            <startPadding
+                launcher:ofAvailableSpace="0.0125" />
+            <endPadding
+                launcher:ofAvailableSpace="0.05" />
+            <!--  value in workspace spec using matchWorkspace -->
+            <gutter
+                launcher:matchWorkspace="true" />
+            <cellSize
+                launcher:ofRemainderSpace="0.2" />
+        </workspaceSpec>
 
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofAvailableSpace="0.0306" />
-        <endPadding
-            launcher:ofAvailableSpace="0.068" />
-        <gutter
-            launcher:fixedSize="16dp" />
-        <cellSize
-            launcher:ofRemainderSpace="0.2" />
-    </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding
+                launcher:ofAvailableSpace="0.0306" />
+            <endPadding
+                launcher:ofAvailableSpace="0.068" />
+            <gutter
+                launcher:fixedSize="16dp" />
+            <cellSize
+                launcher:ofRemainderSpace="0.2" />
+        </workspaceSpec>
 
-    <!-- Width spec is always the same -->
-    <workspaceSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <endPadding
-            launcher:ofRemainderSpace="0.21436227" />
-        <gutter
-            launcher:ofRemainderSpace="0.11425509" />
-        <cellSize
-            launcher:fixedSize="120dp" />
-    </workspaceSpec>
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding
+                launcher:ofRemainderSpace="0.21436227" />
+            <endPadding
+                launcher:ofRemainderSpace="0.21436227" />
+            <gutter
+                launcher:ofRemainderSpace="0.11425509" />
+            <cellSize
+                launcher:fixedSize="120dp" />
+        </workspaceSpec>
+    </specs>
 </workspaceSpecs>
diff --git a/tests/res/xml/valid_all_apps_file.xml b/tests/res/xml/valid_all_apps_file.xml
index 0be55d1..f0967c4 100644
--- a/tests/res/xml/valid_all_apps_file.xml
+++ b/tests/res/xml/valid_all_apps_file.xml
@@ -15,22 +15,23 @@
   -->
 
 <allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <allAppsSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="0dp" />
-        <endPadding launcher:fixedSize="0dp" />
-        <gutter launcher:matchWorkspace="true" />
-        <cellSize launcher:matchWorkspace="true" />
-    </allAppsSpec>
+    <specs launcher:maxAspectRatio="10">
+        <allAppsSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="0dp" />
+            <gutter launcher:matchWorkspace="true" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
 
-    <allAppsSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:matchWorkspace="true" />
-        <endPadding launcher:matchWorkspace="true" />
-        <gutter launcher:matchWorkspace="true" />
-        <cellSize launcher:matchWorkspace="true" />
-    </allAppsSpec>
-
+        <allAppsSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:matchWorkspace="true" />
+            <endPadding launcher:matchWorkspace="true" />
+            <gutter launcher:matchWorkspace="true" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
+    </specs>
 </allAppsSpecs>
diff --git a/tests/res/xml/valid_folders_specs.xml b/tests/res/xml/valid_folders_specs.xml
index 0c45544..2ef59a9 100644
--- a/tests/res/xml/valid_folders_specs.xml
+++ b/tests/res/xml/valid_folders_specs.xml
@@ -14,25 +14,27 @@
   ~ limitations under the License.
   -->
 <folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:matchWorkspace="true" />
-    </folderSpec>
-    <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="16dp" />
-        <endPadding launcher:fixedSize="16dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="102dp" />
-    </folderSpec>
+    <specs launcher:maxAspectRatio="10">
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="800dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </folderSpec>
+        <folderSpec launcher:specType="width" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="102dp" />
+        </folderSpec>
 
-    <!-- Height spec is fixed -->
-    <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="24dp" />
-        <!-- mapped to footer height size -->
-        <endPadding launcher:fixedSize="64dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:matchWorkspace="true" />
-    </folderSpec>
+        <!-- Height spec is fixed -->
+        <folderSpec launcher:specType="height" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="24dp" />
+            <!-- mapped to footer height size -->
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </folderSpec>
+    </specs>
 </folderSpecs>
diff --git a/tests/res/xml/valid_hotseat_file.xml b/tests/res/xml/valid_hotseat_file.xml
index f698bd1..d1c9e49 100644
--- a/tests/res/xml/valid_hotseat_file.xml
+++ b/tests/res/xml/valid_hotseat_file.xml
@@ -14,19 +14,19 @@
   ~ limitations under the License.
   -->
 <hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <hotseatSpec
+            launcher:maxAvailableSize="847dp"
+            launcher:specType="height">
+            <hotseatQsbSpace launcher:fixedSize="24dp" />
+            <edgePadding launcher:fixedSize="48dp" />
+        </hotseatSpec>
 
-    <hotseatSpec
-        launcher:maxAvailableSize="847dp"
-        launcher:specType="height">
-        <hotseatQsbSpace launcher:fixedSize="24dp" />
-        <edgePadding launcher:fixedSize="48dp" />
-    </hotseatSpec>
-
-    <hotseatSpec
-        launcher:maxAvailableSize="9999dp"
-        launcher:specType="height">
-        <hotseatQsbSpace launcher:fixedSize="36dp" />
-        <edgePadding launcher:fixedSize="48dp" />
-    </hotseatSpec>
-
+        <hotseatSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:specType="height">
+            <hotseatQsbSpace launcher:fixedSize="36dp" />
+            <edgePadding launcher:fixedSize="48dp" />
+        </hotseatSpec>
+    </specs>
 </hotseatSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/valid_hotseat_land_file.xml b/tests/res/xml/valid_hotseat_land_file.xml
index fc4a836..a72fd18 100644
--- a/tests/res/xml/valid_hotseat_land_file.xml
+++ b/tests/res/xml/valid_hotseat_land_file.xml
@@ -14,19 +14,19 @@
   ~ limitations under the License.
   -->
 <hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <hotseatSpec
+            launcher:maxAvailableSize="743dp"
+            launcher:specType="width">
+            <hotseatQsbSpace launcher:fixedSize="0dp" />
+            <edgePadding launcher:fixedSize="48dp" />
+        </hotseatSpec>
 
-    <hotseatSpec
-        launcher:maxAvailableSize="743dp"
-        launcher:specType="width">
-        <hotseatQsbSpace launcher:fixedSize="0dp" />
-        <edgePadding launcher:fixedSize="48dp" />
-    </hotseatSpec>
-
-    <hotseatSpec
-        launcher:maxAvailableSize="9999dp"
-        launcher:specType="width">
-        <hotseatQsbSpace launcher:fixedSize="0dp" />
-        <edgePadding launcher:fixedSize="64dp" />
-    </hotseatSpec>
-
+        <hotseatSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:specType="width">
+            <hotseatQsbSpace launcher:fixedSize="0dp" />
+            <edgePadding launcher:fixedSize="64dp" />
+        </hotseatSpec>
+    </specs>
 </hotseatSpecs>
\ No newline at end of file
diff --git a/tests/res/xml/valid_responsive_spec_unsorted.xml b/tests/res/xml/valid_responsive_spec_unsorted.xml
new file mode 100644
index 0000000..7cefc8e
--- /dev/null
+++ b/tests/res/xml/valid_responsive_spec_unsorted.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="10">
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="34dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="371dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="24dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="716dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="36dp" />
+            <endPadding launcher:fixedSize="80dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="602dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="36dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+
+    <!-- specs from land/handheld_workspace_spec_4x4.xml -->
+    <specs launcher:maxAspectRatio="12">
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="2dp" />
+            <endPadding launcher:fixedSize="2dp" />
+            <gutter launcher:fixedSize="8dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="1dp" />
+            <endPadding launcher:fixedSize="1dp" />
+            <gutter launcher:fixedSize="8dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+
+    <specs launcher:maxAspectRatio="1.05">
+        <!-- 584 grid height + 28 remainder space -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="612dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="8dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <!-- 584 grid height -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="584dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="32dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofAvailableSpace="0.15808" />
+        </workspaceSpec>
+
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="22dp" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
+</workspaceSpecs>
diff --git a/tests/res/xml/valid_workspace_file.xml b/tests/res/xml/valid_workspace_file.xml
index 1f97314..da5ca11 100644
--- a/tests/res/xml/valid_workspace_file.xml
+++ b/tests/res/xml/valid_workspace_file.xml
@@ -15,45 +15,93 @@
   -->
 
 <workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
-    <!-- 584 grid height -->
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="584dp">
-        <startPadding launcher:fixedSize="0dp" />
-        <endPadding launcher:fixedSize="32dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:ofAvailableSpace="0.15808" />
-    </workspaceSpec>
+    <specs launcher:maxAspectRatio="1.05">
+        <!-- 584 grid height -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="584dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="32dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofAvailableSpace="0.15808" />
+        </workspaceSpec>
 
-    <!-- 584 grid height + 28 remainder space -->
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="612dp">
-        <startPadding launcher:fixedSize="0dp" />
-        <endPadding launcher:ofRemainderSpace="1" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="104dp" />
-    </workspaceSpec>
+        <!-- 584 grid height + 28 remainder space -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="612dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
 
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="8dp" />
-        <endPadding launcher:ofRemainderSpace="1" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="104dp" />
-    </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="8dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
 
-    <!--  TODO(b/241386436): other specs here for height ...  -->
+        <!--  TODO(b/241386436): other specs here for height ...  -->
 
-    <!-- Width spec is always the same -->
-    <workspaceSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="22dp" />
-        <endPadding launcher:fixedSize="22dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:ofRemainderSpace="0.25" />
-    </workspaceSpec>
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="22dp" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
 
+    <!-- specs from land/handheld_workspace_spec_4x4.xml -->
+    <specs launcher:maxAspectRatio="99999">
+        <!-- Height spec -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="371dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="24dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="34dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+
+        <!-- Width spec -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="602dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="36dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="716dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="36dp" />
+            <endPadding launcher:fixedSize="80dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
 </workspaceSpecs>
diff --git a/tests/res/xml/valid_workspace_unsorted_file.xml b/tests/res/xml/valid_workspace_unsorted_file.xml
index 1216c81..9f2bdc3 100644
--- a/tests/res/xml/valid_workspace_unsorted_file.xml
+++ b/tests/res/xml/valid_workspace_unsorted_file.xml
@@ -15,44 +15,44 @@
   -->
 
 <workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="1.05">
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="8dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
 
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="8dp" />
-        <endPadding launcher:ofRemainderSpace="1" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="104dp" />
-    </workspaceSpec>
+        <!-- 584 grid height -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="584dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="32dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofAvailableSpace="0.15808" />
+        </workspaceSpec>
 
-    <!-- 584 grid height -->
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="584dp">
-        <startPadding launcher:fixedSize="0dp" />
-        <endPadding launcher:fixedSize="32dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:ofAvailableSpace="0.15808" />
-    </workspaceSpec>
+        <!-- 584 grid height + 28 remainder space -->
+        <workspaceSpec
+            launcher:specType="height"
+            launcher:maxAvailableSize="612dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
 
-    <!-- 584 grid height + 28 remainder space -->
-    <workspaceSpec
-        launcher:specType="height"
-        launcher:maxAvailableSize="612dp">
-        <startPadding launcher:fixedSize="0dp" />
-        <endPadding launcher:ofRemainderSpace="1" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:fixedSize="104dp" />
-    </workspaceSpec>
-
-    <!-- Width spec is always the same -->
-    <workspaceSpec
-        launcher:specType="width"
-        launcher:maxAvailableSize="9999dp">
-        <startPadding launcher:fixedSize="22dp" />
-        <endPadding launcher:fixedSize="22dp" />
-        <gutter launcher:fixedSize="16dp" />
-        <cellSize launcher:ofRemainderSpace="0.25" />
-    </workspaceSpec>
-
+        <!-- Width spec is always the same -->
+        <workspaceSpec
+            launcher:specType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="22dp" />
+            <endPadding launcher:fixedSize="22dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+    </specs>
 </workspaceSpecs>
diff --git a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
new file mode 100644
index 0000000..80f73cb
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.allapps;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.view.KeyEvent;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaplKeyboardFocusTest extends AbstractLauncherUiTest {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        initialize(this);
+    }
+
+    @Test
+    public void testAllAppsFocusApp() {
+        final HomeAllApps allApps = mLauncher.goHome().switchToAllApps();
+        assertTrue("Launcher internal state is not All Apps",
+                isInState(() -> LauncherState.ALL_APPS));
+        allApps.freeze();
+        try {
+            mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
+            executeOnLauncher(launcher -> assertNotNull("No focused child.",
+                    launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()));
+        } finally {
+            allApps.unfreeze();
+        }
+    }
+
+    @Test
+    public void testAllAppsExitSearchAndFocusApp() {
+        final HomeAllApps allApps = mLauncher.goHome().switchToAllApps();
+        assertTrue("Launcher internal state is not All Apps",
+                isInState(() -> LauncherState.ALL_APPS));
+        allApps.freeze();
+        try {
+            executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus());
+            waitForLauncherCondition("Search view does not have focus.",
+                    launcher -> launcher.getAppsView().getSearchView().hasFocus());
+
+            mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
+            executeOnLauncher(launcher -> assertNotNull("No focused child.",
+                    launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()));
+        } finally {
+            allApps.unfreeze();
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 036f2d8..def27b8 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -1,10 +1,12 @@
 package com.android.launcher3.model
 
-import android.appwidget.AppWidgetManager
+import android.content.Context
 import android.os.UserHandle
+import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Flags
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherModel
@@ -12,19 +14,25 @@
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.icons.cache.CachingLogic
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler
+import com.android.launcher3.pm.UserCache
 import com.android.launcher3.util.Executors.MODEL_EXECUTOR
 import com.android.launcher3.util.LooperIdleLock
+import com.android.launcher3.util.UserIconInfo
+import com.android.launcher3.util.rule.StaticMockitoRule
 import com.google.common.truth.Truth
 import java.util.concurrent.CountDownLatch
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.Spy
 
 private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql"
 
@@ -40,7 +48,13 @@
     @Mock private lateinit var iconCache: IconCache
     @Mock private lateinit var idleLock: LooperIdleLock
     @Mock private lateinit var iconCacheUpdateHandler: IconCacheUpdateHandler
-    @Mock private lateinit var appWidgetManager: AppWidgetManager
+    @Mock private lateinit var userCache: UserCache
+
+    @Spy private var userManagerState: UserManagerState? = UserManagerState()
+
+    @get:Rule(order = 0) val staticMockitoRule = StaticMockitoRule(UserCache::class.java)
+    @get:Rule(order = 1)
+    val setFlagsRule = SetFlagsRule().apply { initAllFlagsToReleaseConfigDefault() }
 
     @Before
     fun setup() {
@@ -63,8 +77,7 @@
         `when`(launcherBinder.newIdleLock(any(LoaderTask::class.java))).thenReturn(idleLock)
         `when`(idleLock.awaitLocked(1000)).thenReturn(false)
         `when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler)
-        `when`(appWidgetManager.getInstalledProvidersForProfile(any(UserHandle::class.java)))
-            .thenReturn(emptyList())
+        `when`(UserCache.getInstance(any(Context::class.java))).thenReturn(userCache)
     }
 
     @Test
@@ -95,6 +108,48 @@
         verify(modelDelegate).modelLoadComplete()
         verify(transaction).commit()
     }
+
+    @Test
+    fun setsQuietModeFlagCorrectlyForWorkProfile() =
+        with(BgDataModel()) {
+            setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE)
+            val MAIN_HANDLE = UserHandle.of(0)
+            val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
+            `when`(userCache.userProfiles).thenReturn(mockUserHandles)
+            `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
+            `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1))
+
+            LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState)
+                .runSyncOnBackgroundThread()
+
+            verify(bgAllAppsList)
+                .setFlags(BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED, true)
+            verify(bgAllAppsList)
+                .setFlags(BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED, false)
+            verify(bgAllAppsList, Mockito.never())
+                .setFlags(BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED, true)
+        }
+
+    @Test
+    fun setsQuietModeFlagCorrectlyForPrivateProfile() =
+        with(BgDataModel()) {
+            setFlagsRule.enableFlags(Flags.FLAG_ENABLE_PRIVATE_SPACE)
+            val MAIN_HANDLE = UserHandle.of(0)
+            val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
+            `when`(userCache.userProfiles).thenReturn(mockUserHandles)
+            `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
+            `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 3))
+
+            LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState)
+                .runSyncOnBackgroundThread()
+
+            verify(bgAllAppsList)
+                .setFlags(BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED, false)
+            verify(bgAllAppsList)
+                .setFlags(BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED, true)
+            verify(bgAllAppsList, Mockito.never())
+                .setFlags(BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED, true)
+        }
 }
 
 private fun LoaderTask.runSyncOnBackgroundThread() {
diff --git a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt b/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
index cd95e99..c99da96 100644
--- a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
@@ -32,22 +33,30 @@
 @RunWith(AndroidJUnit4::class)
 class AllAppsSpecsTest : AbstractDeviceProfileTest() {
     override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+    val deviceSpec = deviceSpecs["phone"]!!
+    val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
 
     @Before
     fun setup() {
-        initializeVarsForPhone(deviceSpecs["phone"]!!)
+        initializeVarsForPhone(deviceSpec)
     }
 
     @Test
     fun parseValidFile() {
         val allAppsSpecs =
-            AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_all_apps_file))
-        assertThat(allAppsSpecs.heightSpecs.size).isEqualTo(1)
-        assertThat(allAppsSpecs.heightSpecs[0].toString())
+            ResponsiveSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_all_apps_file),
+                ResponsiveSpecType.AllApps
+            )
+
+        val specs = allAppsSpecs.getSpecsByAspectRatio(aspectRatio)
+        assertThat(specs.heightSpecs.size).isEqualTo(1)
+        assertThat(specs.heightSpecs[0].toString())
             .isEqualTo(
-                "AllAppsSpec(" +
+                "ResponsiveSpec(" +
                     "maxAvailableSize=26247, " +
-                    "specType=HEIGHT, " +
+                    "dimensionType=HEIGHT, " +
+                    "specType=AllApps, " +
                     "startPadding=SizeSpec(fixedSize=0.0, " +
                     "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0, " +
@@ -71,12 +80,13 @@
                     ")"
             )
 
-        assertThat(allAppsSpecs.widthSpecs.size).isEqualTo(1)
-        assertThat(allAppsSpecs.widthSpecs[0].toString())
+        assertThat(specs.widthSpecs.size).isEqualTo(1)
+        assertThat(specs.widthSpecs[0].toString())
             .isEqualTo(
-                "AllAppsSpec(" +
+                "ResponsiveSpec(" +
                     "maxAvailableSize=26247, " +
-                    "specType=WIDTH, " +
+                    "dimensionType=WIDTH, " +
+                    "specType=AllApps, " +
                     "startPadding=SizeSpec(fixedSize=0.0, " +
                     "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0, " +
@@ -103,16 +113,25 @@
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingTag_throwsError() {
-        AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_1))
+        ResponsiveSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_1),
+            ResponsiveSpecType.AllApps
+        )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
-        AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_2))
+        ResponsiveSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_2),
+            ResponsiveSpecType.AllApps
+        )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_valueBiggerThan1_throwsError() {
-        AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_3))
+        ResponsiveSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_3),
+            ResponsiveSpecType.AllApps
+        )
     }
 }
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
index 0f12e58..1cc5ed2 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
@@ -21,6 +21,8 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
 import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
@@ -40,6 +42,7 @@
     @Test
     fun normalPhone_copiesFromWorkspace() {
         val deviceSpec = deviceSpecs["phone"]!!
+        val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
         initializeVarsForPhone(deviceSpec)
 
         val availableWidth = deviceSpec.naturalSize.first
@@ -48,14 +51,30 @@
         val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 495
 
         val workspaceSpecs =
-            WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
-        val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
-        val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+            ResponsiveSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                ResponsiveSpecType.Workspace
+            )
+        val widthSpec =
+            workspaceSpecs.getCalculatedSpec(aspectRatio, DimensionType.WIDTH, 4, availableWidth)
+        val heightSpec =
+            workspaceSpecs.getCalculatedSpec(aspectRatio, DimensionType.HEIGHT, 5, availableHeight)
 
         val allAppsSpecs =
-            AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_all_apps_file))
+            ResponsiveSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_all_apps_file),
+                ResponsiveSpecType.AllApps
+            )
 
-        with(allAppsSpecs.getCalculatedWidthSpec(4, availableWidth, widthSpec)) {
+        with(
+            allAppsSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.WIDTH,
+                4,
+                availableWidth,
+                widthSpec
+            )
+        ) {
             assertThat(availableSpace).isEqualTo(availableWidth)
             assertThat(cells).isEqualTo(4)
             assertThat(startPaddingPx).isEqualTo(widthSpec.startPaddingPx)
@@ -64,7 +83,15 @@
             assertThat(cellSizePx).isEqualTo(widthSpec.cellSizePx)
         }
 
-        with(allAppsSpecs.getCalculatedHeightSpec(5, availableHeight, heightSpec)) {
+        with(
+            allAppsSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.HEIGHT,
+                5,
+                availableHeight,
+                heightSpec
+            )
+        ) {
             assertThat(availableSpace).isEqualTo(availableHeight)
             assertThat(cells).isEqualTo(5)
             assertThat(startPaddingPx).isEqualTo(0)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
new file mode 100644
index 0000000..c4e2d2a
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.responsive
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
+import com.android.launcher3.tests.R
+import com.android.launcher3.util.TestResourceHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CalculatedFolderSpecTest : AbstractDeviceProfileTest() {
+    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+    private val deviceSpec = deviceSpecs["phone"]!!
+    private val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
+
+    @Before
+    fun setup() {
+        initializeVarsForPhone(deviceSpec)
+    }
+
+    @Test
+    fun validate_matchWidthWorkspace() {
+        val columns = 6
+
+        // Loading workspace specs
+        val resourceHelperWorkspace = TestResourceHelper(context, R.xml.valid_workspace_file)
+        val workspaceSpecs =
+            ResponsiveSpecsProvider.create(resourceHelperWorkspace, ResponsiveSpecType.Workspace)
+
+        // Loading folders specs
+        val resourceHelperFolder = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val folderSpecs =
+            ResponsiveSpecsProvider.create(resourceHelperFolder, ResponsiveSpecType.Folder)
+        val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
+
+        assertThat(specs.widthSpecs.size).isEqualTo(2)
+        assertThat(specs.widthSpecs[0].cellSize.matchWorkspace).isEqualTo(true)
+        assertThat(specs.widthSpecs[1].cellSize.matchWorkspace).isEqualTo(false)
+
+        // Validate width spec <= 800
+        var availableWidth = deviceSpec.naturalSize.first
+        var calculatedWorkspace =
+            workspaceSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.WIDTH,
+                columns,
+                availableWidth
+            )
+        var calculatedWidthFolderSpec =
+            folderSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.WIDTH,
+                columns,
+                availableWidth,
+                calculatedWorkspace
+            )
+        with(calculatedWidthFolderSpec) {
+            assertThat(availableSpace).isEqualTo(availableWidth)
+            assertThat(cells).isEqualTo(columns)
+            assertThat(startPaddingPx).isEqualTo(16.dpToPx())
+            assertThat(endPaddingPx).isEqualTo(16.dpToPx())
+            assertThat(gutterPx).isEqualTo(16.dpToPx())
+            assertThat(cellSizePx).isEqualTo(calculatedWorkspace.cellSizePx)
+        }
+
+        // Validate width spec > 800
+        availableWidth = 2000.dpToPx()
+        calculatedWorkspace =
+            workspaceSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.WIDTH,
+                columns,
+                availableWidth
+            )
+        calculatedWidthFolderSpec =
+            folderSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.WIDTH,
+                columns,
+                availableWidth,
+                calculatedWorkspace
+            )
+        with(calculatedWidthFolderSpec) {
+            assertThat(availableSpace).isEqualTo(availableWidth)
+            assertThat(cells).isEqualTo(columns)
+            assertThat(startPaddingPx).isEqualTo(16.dpToPx())
+            assertThat(endPaddingPx).isEqualTo(16.dpToPx())
+            assertThat(gutterPx).isEqualTo(16.dpToPx())
+            assertThat(cellSizePx).isEqualTo(102.dpToPx())
+        }
+    }
+
+    @Test
+    fun validate_matchHeightWorkspace() {
+        // Hotseat is roughly 495px on a real device, it doesn't need to be precise on unit tests
+        val hotseatSize = 495
+        val statusBarHeight = deviceSpec.statusBarNaturalPx
+        val availableHeight = deviceSpec.naturalSize.second - statusBarHeight - hotseatSize
+        val rows = 5
+
+        // Loading workspace specs
+        val resourceHelperWorkspace = TestResourceHelper(context, R.xml.valid_workspace_file)
+        val workspaceSpecs =
+            ResponsiveSpecsProvider.create(resourceHelperWorkspace, ResponsiveSpecType.Workspace)
+
+        // Loading folders specs
+        val resourceHelperFolder = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val folderSpecs =
+            ResponsiveSpecsProvider.create(resourceHelperFolder, ResponsiveSpecType.Folder)
+        val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
+
+        assertThat(specs.heightSpecs.size).isEqualTo(1)
+        assertThat(specs.heightSpecs[0].cellSize.matchWorkspace).isEqualTo(true)
+
+        // Validate height spec
+        val calculatedWorkspace =
+            workspaceSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.HEIGHT,
+                rows,
+                availableHeight
+            )
+        val calculatedFolderSpec =
+            folderSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.HEIGHT,
+                rows,
+                availableHeight,
+                calculatedWorkspace
+            )
+        with(calculatedFolderSpec) {
+            assertThat(availableSpace).isEqualTo(availableHeight)
+            assertThat(cells).isEqualTo(rows)
+            assertThat(startPaddingPx).isEqualTo(24.dpToPx())
+            assertThat(endPaddingPx).isEqualTo(64.dpToPx())
+            assertThat(gutterPx).isEqualTo(16.dpToPx())
+            assertThat(cellSizePx).isEqualTo(calculatedWorkspace.cellSizePx)
+        }
+    }
+}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
deleted file mode 100644
index f2a269a..0000000
--- a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.responsive
-
-import android.content.Context
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.launcher3.AbstractDeviceProfileTest
-import com.android.launcher3.tests.R
-import com.android.launcher3.util.TestResourceHelper
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CalculatedFolderSpecsTest : AbstractDeviceProfileTest() {
-    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
-
-    private val deviceSpec = deviceSpecs["phone"]!!
-
-    @Before
-    fun setup() {
-        initializeVarsForPhone(deviceSpec)
-    }
-
-    @Test
-    fun validate_matchWidthWorkspace() {
-        val columns = 6
-
-        // Loading workspace specs
-        val resourceHelperWorkspace = TestResourceHelper(context!!, R.xml.valid_workspace_file)
-        val workspaceSpecs = WorkspaceSpecs.create(resourceHelperWorkspace)
-
-        // Loading folders specs
-        val resourceHelperFolder = TestResourceHelper(context!!, R.xml.valid_folders_specs)
-        val folderSpecs = FolderSpecs.create(resourceHelperFolder)
-
-        assertThat(folderSpecs.widthSpecs.size).isEqualTo(2)
-        assertThat(folderSpecs.widthSpecs[0].cellSize.matchWorkspace).isEqualTo(true)
-        assertThat(folderSpecs.widthSpecs[1].cellSize.matchWorkspace).isEqualTo(false)
-
-        // Validate width spec <= 800
-        var availableWidth = deviceSpec.naturalSize.first
-        var calculatedWorkspace = workspaceSpecs.getCalculatedWidthSpec(columns, availableWidth)
-        var calculatedWidthFolderSpec =
-            folderSpecs.getCalculatedWidthSpec(columns, availableWidth, calculatedWorkspace)
-        with(calculatedWidthFolderSpec) {
-            assertThat(availableSpace).isEqualTo(availableWidth)
-            assertThat(cells).isEqualTo(columns)
-            assertThat(startPaddingPx).isEqualTo(16.dpToPx())
-            assertThat(endPaddingPx).isEqualTo(16.dpToPx())
-            assertThat(gutterPx).isEqualTo(16.dpToPx())
-            assertThat(cellSizePx).isEqualTo(calculatedWorkspace.cellSizePx)
-        }
-
-        // Validate width spec > 800
-        availableWidth = 2000.dpToPx()
-        calculatedWorkspace = workspaceSpecs.getCalculatedWidthSpec(columns, availableWidth)
-        calculatedWidthFolderSpec =
-            folderSpecs.getCalculatedWidthSpec(columns, availableWidth, calculatedWorkspace)
-        with(calculatedWidthFolderSpec) {
-            assertThat(availableSpace).isEqualTo(availableWidth)
-            assertThat(cells).isEqualTo(columns)
-            assertThat(startPaddingPx).isEqualTo(16.dpToPx())
-            assertThat(endPaddingPx).isEqualTo(16.dpToPx())
-            assertThat(gutterPx).isEqualTo(16.dpToPx())
-            assertThat(cellSizePx).isEqualTo(102.dpToPx())
-        }
-    }
-
-    @Test
-    fun validate_matchHeightWorkspace() {
-        // Hotseat is roughly 495px on a real device, it doesn't need to be precise on unit tests
-        val hotseatSize = 495
-        val statusBarHeight = deviceSpec.statusBarNaturalPx
-        val availableHeight = deviceSpec.naturalSize.second - statusBarHeight - hotseatSize
-        val rows = 5
-
-        // Loading workspace specs
-        val resourceHelperWorkspace = TestResourceHelper(context!!, R.xml.valid_workspace_file)
-        val workspaceSpecs = WorkspaceSpecs.create(resourceHelperWorkspace)
-
-        // Loading folders specs
-        val resourceHelperFolder = TestResourceHelper(context!!, R.xml.valid_folders_specs)
-        val folderSpecs = FolderSpecs.create(resourceHelperFolder)
-
-        assertThat(folderSpecs.heightSpecs.size).isEqualTo(1)
-        assertThat(folderSpecs.heightSpecs[0].cellSize.matchWorkspace).isEqualTo(true)
-
-        // Validate height spec
-        val calculatedWorkspace = workspaceSpecs.getCalculatedHeightSpec(rows, availableHeight)
-        val calculatedFolderSpec =
-            folderSpecs.getCalculatedHeightSpec(rows, availableHeight, calculatedWorkspace)
-        with(calculatedFolderSpec) {
-            assertThat(availableSpace).isEqualTo(availableHeight)
-            assertThat(cells).isEqualTo(rows)
-            assertThat(startPaddingPx).isEqualTo(24.dpToPx())
-            assertThat(endPaddingPx).isEqualTo(64.dpToPx())
-            assertThat(gutterPx).isEqualTo(16.dpToPx())
-            assertThat(cellSizePx).isEqualTo(calculatedWorkspace.cellSizePx)
-        }
-    }
-}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
index 5865036..1a564ac 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
 import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
@@ -31,6 +32,8 @@
 @RunWith(AndroidJUnit4::class)
 class CalculatedHotseatSpecTest : AbstractDeviceProfileTest() {
     override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+    val deviceSpec = deviceSpecs["phone"]!!
+    val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
 
     /**
      * This test tests:
@@ -38,15 +41,19 @@
      */
     @Test
     fun normalPhone_returnsSecondBreakpointSpec() {
-        val deviceSpec = deviceSpecs["phone"]!!
         initializeVarsForPhone(deviceSpec)
 
         // Hotseat uses the whole device height
         val availableHeight = deviceSpec.naturalSize.second
 
-        val hotseatSpecs =
-            HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
-        val heightSpec = hotseatSpecs.getCalculatedHeightSpec(availableHeight)
+        val hotseatSpecsProvider =
+            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+        val heightSpec =
+            hotseatSpecsProvider.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.HEIGHT,
+                availableHeight
+            )
 
         assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
         assertThat(heightSpec.hotseatQsbSpace).isEqualTo(95)
@@ -59,16 +66,20 @@
      */
     @Test
     fun smallPhone_returnsFirstBreakpointSpec() {
-        val deviceSpec = deviceSpecs["phone"]!!
         deviceSpec.densityDpi = 540 // larger display size
         initializeVarsForPhone(deviceSpec)
 
         // Hotseat uses the whole device height
         val availableHeight = deviceSpec.naturalSize.second
 
-        val hotseatSpecs =
-            HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
-        val heightSpec = hotseatSpecs.getCalculatedHeightSpec(availableHeight)
+        val hotseatSpecsProvider =
+            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+        val heightSpec =
+            hotseatSpecsProvider.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.HEIGHT,
+                availableHeight
+            )
 
         assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
         assertThat(heightSpec.hotseatQsbSpace).isEqualTo(81)
@@ -81,15 +92,17 @@
      */
     @Test
     fun normalPhoneLandscape_returnsSecondBreakpointSpec() {
-        val deviceSpec = deviceSpecs["phone"]!!
         initializeVarsForPhone(deviceSpec, isVerticalBar = true)
 
         // Hotseat uses the whole device width
         val availableWidth = deviceSpec.naturalSize.second
 
-        val hotseatSpecs =
-            HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_land_file))
-        val widthSpec = hotseatSpecs.getCalculatedWidthSpec(availableWidth)
+        val hotseatSpecsProvider =
+            HotseatSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_hotseat_land_file)
+            )
+        val widthSpec =
+            hotseatSpecsProvider.getCalculatedSpec(aspectRatio, DimensionType.WIDTH, availableWidth)
 
         assertThat(widthSpec.availableSpace).isEqualTo(availableWidth)
         assertThat(widthSpec.hotseatQsbSpace).isEqualTo(0)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
index 8f56c5f..0c5d347 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
@@ -21,6 +21,8 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
 import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
@@ -41,6 +43,7 @@
     @Test
     fun normalPhone_returnsThirdBreakpointSpec() {
         val deviceSpec = deviceSpecs["phone"]!!
+        val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
         initializeVarsForPhone(deviceSpec)
 
         val availableWidth = deviceSpec.naturalSize.first
@@ -49,9 +52,14 @@
         val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 495
 
         val workspaceSpecs =
-            WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
-        val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
-        val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+            ResponsiveSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                ResponsiveSpecType.Workspace
+            )
+        val widthSpec =
+            workspaceSpecs.getCalculatedSpec(aspectRatio, DimensionType.WIDTH, 4, availableWidth)
+        val heightSpec =
+            workspaceSpecs.getCalculatedSpec(aspectRatio, DimensionType.HEIGHT, 5, availableHeight)
 
         assertThat(widthSpec.availableSpace).isEqualTo(availableWidth)
         assertThat(widthSpec.cells).isEqualTo(4)
@@ -77,6 +85,7 @@
     @Test
     fun smallPhone_returnsFirstBreakpointSpec() {
         val deviceSpec = deviceSpecs["phone"]!!
+        val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
         deviceSpec.densityDpi = 540 // larger display size
         initializeVarsForPhone(deviceSpec)
 
@@ -86,9 +95,14 @@
         val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
 
         val workspaceSpecs =
-            WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
-        val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
-        val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+            ResponsiveSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                ResponsiveSpecType.Workspace
+            )
+        val widthSpec =
+            workspaceSpecs.getCalculatedSpec(aspectRatio, DimensionType.WIDTH, 4, availableWidth)
+        val heightSpec =
+            workspaceSpecs.getCalculatedSpec(aspectRatio, DimensionType.HEIGHT, 5, availableHeight)
 
         assertThat(widthSpec.availableSpace).isEqualTo(availableWidth)
         assertThat(widthSpec.cells).isEqualTo(4)
@@ -114,6 +128,7 @@
     @Test
     fun smallPhone_returnsFirstBreakpointSpec_unsortedFile() {
         val deviceSpec = deviceSpecs["phone"]!!
+        val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
         deviceSpec.densityDpi = 540 // larger display size
         initializeVarsForPhone(deviceSpec)
 
@@ -121,13 +136,15 @@
         // Hotseat size is roughly 640px on a real device,
         // it doesn't need to be precise on unit tests
         val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
-
         val workspaceSpecs =
-            WorkspaceSpecs.create(
-                TestResourceHelper(context!!, TestR.xml.valid_workspace_unsorted_file)
+            ResponsiveSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_workspace_unsorted_file),
+                ResponsiveSpecType.Workspace
             )
-        val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
-        val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+        val widthSpec =
+            workspaceSpecs.getCalculatedSpec(aspectRatio, DimensionType.WIDTH, 4, availableWidth)
+        val heightSpec =
+            workspaceSpecs.getCalculatedSpec(aspectRatio, DimensionType.HEIGHT, 5, availableHeight)
 
         assertThat(widthSpec.availableSpace).isEqualTo(availableWidth)
         assertThat(widthSpec.cells).isEqualTo(4)
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt b/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
new file mode 100644
index 0000000..1756c9a
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
@@ -0,0 +1,300 @@
+/*
+ * 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.responsive
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
+import com.android.launcher3.tests.R
+import com.android.launcher3.util.TestResourceHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FolderSpecTest : AbstractDeviceProfileTest() {
+    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+    val deviceSpec = deviceSpecs["tablet"]!!
+    val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
+
+    @Before
+    fun setup() {
+        initializeVarsForPhone(deviceSpec)
+    }
+
+    @Test
+    fun parseValidFile() {
+        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+        val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
+
+        val sizeSpec16 = SizeSpec(16f.dpToPx())
+        val widthSpecsExpected =
+            listOf(
+                ResponsiveSpec(
+                    maxAvailableSize = 800.dpToPx(),
+                    dimensionType = DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Folder,
+                    startPadding = sizeSpec16,
+                    endPadding = sizeSpec16,
+                    gutter = sizeSpec16,
+                    cellSize = SizeSpec(matchWorkspace = true)
+                ),
+                ResponsiveSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Folder,
+                    startPadding = sizeSpec16,
+                    endPadding = sizeSpec16,
+                    gutter = sizeSpec16,
+                    cellSize = SizeSpec(102f.dpToPx())
+                )
+            )
+
+        val heightSpecsExpected =
+            ResponsiveSpec(
+                maxAvailableSize = 9999.dpToPx(),
+                dimensionType = DimensionType.HEIGHT,
+                specType = ResponsiveSpecType.Folder,
+                startPadding = SizeSpec(24f.dpToPx()),
+                endPadding = SizeSpec(64f.dpToPx()),
+                gutter = sizeSpec16,
+                cellSize = SizeSpec(matchWorkspace = true)
+            )
+
+        assertThat(specs.widthSpecs.size).isEqualTo(widthSpecsExpected.size)
+        assertThat(specs.widthSpecs[0]).isEqualTo(widthSpecsExpected[0])
+        assertThat(specs.widthSpecs[1]).isEqualTo(widthSpecsExpected[1])
+
+        assertThat(specs.heightSpecs.size).isEqualTo(1)
+        assertThat(specs.heightSpecs[0]).isEqualTo(heightSpecsExpected)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_missingTag_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_1)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_2)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_valueBiggerThan1_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_3)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_missingSpecs_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_4)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_missingWidthBreakpoint_throwsError() {
+        val availableSpace = 900.dpToPx()
+        val cells = 3
+
+        val workspaceSpec =
+            ResponsiveSpec(
+                maxAvailableSize = availableSpace,
+                dimensionType = DimensionType.WIDTH,
+                specType = ResponsiveSpecType.Folder,
+                startPadding = SizeSpec(fixedSize = 10f),
+                endPadding = SizeSpec(fixedSize = 10f),
+                gutter = SizeSpec(fixedSize = 10f),
+                cellSize = SizeSpec(fixedSize = 10f)
+            )
+        val calculatedWorkspaceSpec = CalculatedResponsiveSpec(availableSpace, cells, workspaceSpec)
+
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_5)
+        val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+        folderSpecs.getCalculatedSpec(
+            aspectRatio,
+            DimensionType.WIDTH,
+            cells,
+            availableSpace,
+            calculatedWorkspaceSpec
+        )
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_missingHeightBreakpoint_throwsError() {
+        val availableSpace = 900.dpToPx()
+        val cells = 3
+
+        val workspaceSpec =
+            ResponsiveSpec(
+                maxAvailableSize = availableSpace,
+                dimensionType = DimensionType.HEIGHT,
+                specType = ResponsiveSpecType.Folder,
+                startPadding = SizeSpec(fixedSize = 10f),
+                endPadding = SizeSpec(fixedSize = 10f),
+                gutter = SizeSpec(fixedSize = 10f),
+                cellSize = SizeSpec(fixedSize = 10f)
+            )
+        val calculatedWorkspaceSpec = CalculatedResponsiveSpec(availableSpace, cells, workspaceSpec)
+
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_5)
+        val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+        folderSpecs.getCalculatedSpec(
+            aspectRatio,
+            DimensionType.HEIGHT,
+            cells,
+            availableSpace,
+            calculatedWorkspaceSpec
+        )
+    }
+
+    @Test
+    fun retrievesCalculatedWidthSpec() {
+        val availableSpace = 800.dpToPx()
+        val cells = 3
+
+        val workspaceSpec =
+            ResponsiveSpec(
+                maxAvailableSize = availableSpace,
+                dimensionType = DimensionType.WIDTH,
+                specType = ResponsiveSpecType.Workspace,
+                startPadding = SizeSpec(fixedSize = 10f),
+                endPadding = SizeSpec(fixedSize = 10f),
+                gutter = SizeSpec(fixedSize = 10f),
+                cellSize = SizeSpec(fixedSize = 10f)
+            )
+        val calculatedWorkspaceSpec = CalculatedResponsiveSpec(availableSpace, cells, workspaceSpec)
+
+        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+        val calculatedWidthSpec =
+            folderSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.WIDTH,
+                cells,
+                availableSpace,
+                calculatedWorkspaceSpec
+            )
+
+        assertThat(calculatedWidthSpec.cells).isEqualTo(cells)
+        assertThat(calculatedWidthSpec.availableSpace).isEqualTo(availableSpace)
+        assertThat(calculatedWidthSpec.startPaddingPx).isEqualTo(16.dpToPx())
+        assertThat(calculatedWidthSpec.endPaddingPx).isEqualTo(16.dpToPx())
+        assertThat(calculatedWidthSpec.gutterPx).isEqualTo(16.dpToPx())
+        assertThat(calculatedWidthSpec.cellSizePx).isEqualTo(calculatedWorkspaceSpec.cellSizePx)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun retrievesCalculatedWidthSpec_invalidCalculatedResponsiveSpecType_throwsError() {
+        val availableSpace = 10.dpToPx()
+        val cells = 3
+
+        val workspaceSpec =
+            ResponsiveSpec(
+                maxAvailableSize = availableSpace,
+                dimensionType = DimensionType.HEIGHT,
+                specType = ResponsiveSpecType.Folder,
+                startPadding = SizeSpec(fixedSize = 10f),
+                endPadding = SizeSpec(fixedSize = 10f),
+                gutter = SizeSpec(fixedSize = 10f),
+                cellSize = SizeSpec(fixedSize = 10f)
+            )
+        val calculatedWorkspaceSpec = CalculatedResponsiveSpec(availableSpace, cells, workspaceSpec)
+
+        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+        folderSpecs.getCalculatedSpec(
+            aspectRatio,
+            DimensionType.WIDTH,
+            cells,
+            availableSpace,
+            calculatedWorkspaceSpec
+        )
+    }
+
+    @Test
+    fun retrievesCalculatedHeightSpec() {
+        val availableSpace = 700.dpToPx()
+        val cells = 3
+
+        val workspaceSpec =
+            ResponsiveSpec(
+                maxAvailableSize = availableSpace,
+                dimensionType = DimensionType.HEIGHT,
+                specType = ResponsiveSpecType.Workspace,
+                startPadding = SizeSpec(fixedSize = 10f),
+                endPadding = SizeSpec(fixedSize = 10f),
+                gutter = SizeSpec(fixedSize = 10f),
+                cellSize = SizeSpec(fixedSize = 10f)
+            )
+        val calculatedWorkspaceSpec = CalculatedResponsiveSpec(availableSpace, cells, workspaceSpec)
+
+        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+        val calculatedHeightSpec =
+            folderSpecs.getCalculatedSpec(
+                aspectRatio,
+                DimensionType.HEIGHT,
+                cells,
+                availableSpace,
+                calculatedWorkspaceSpec
+            )
+
+        assertThat(calculatedHeightSpec.cells).isEqualTo(cells)
+        assertThat(calculatedHeightSpec.availableSpace).isEqualTo(availableSpace)
+        assertThat(calculatedHeightSpec.startPaddingPx).isEqualTo(24.dpToPx())
+        assertThat(calculatedHeightSpec.endPaddingPx).isEqualTo(64.dpToPx())
+        assertThat(calculatedHeightSpec.gutterPx).isEqualTo(16.dpToPx())
+        assertThat(calculatedHeightSpec.cellSizePx).isEqualTo(calculatedWorkspaceSpec.cellSizePx)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun retrievesCalculatedHeightSpec_invalidCalculatedResponsiveSpecType_throwsError() {
+        val availableSpace = 10.dpToPx()
+        val cells = 3
+
+        val workspaceSpec =
+            ResponsiveSpec(
+                maxAvailableSize = availableSpace,
+                dimensionType = DimensionType.WIDTH,
+                specType = ResponsiveSpecType.Folder,
+                startPadding = SizeSpec(fixedSize = 10f),
+                endPadding = SizeSpec(fixedSize = 10f),
+                gutter = SizeSpec(fixedSize = 10f),
+                cellSize = SizeSpec(fixedSize = 10f)
+            )
+        val calculatedWorkspaceSpec = CalculatedResponsiveSpec(availableSpace, cells, workspaceSpec)
+
+        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
+        folderSpecs.getCalculatedSpec(
+            aspectRatio,
+            DimensionType.HEIGHT,
+            cells,
+            availableSpace,
+            calculatedWorkspaceSpec
+        )
+    }
+}
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
deleted file mode 100644
index 4b05949..0000000
--- a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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.responsive
-
-import android.content.Context
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.launcher3.AbstractDeviceProfileTest
-import com.android.launcher3.responsive.ResponsiveSpec.SpecType
-import com.android.launcher3.tests.R
-import com.android.launcher3.util.TestResourceHelper
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class FolderSpecsTest : AbstractDeviceProfileTest() {
-    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
-
-    @Before
-    fun setup() {
-        initializeVarsForPhone(deviceSpecs["tablet"]!!)
-    }
-
-    @Test
-    fun parseValidFile() {
-        val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
-        val folderSpecs = FolderSpecs.create(resourceHelper)
-
-        val sizeSpec16 = SizeSpec(16f.dpToPx())
-        val widthSpecsExpected =
-            listOf(
-                FolderSpec(
-                    maxAvailableSize = 800.dpToPx(),
-                    specType = SpecType.WIDTH,
-                    startPadding = sizeSpec16,
-                    endPadding = sizeSpec16,
-                    gutter = sizeSpec16,
-                    cellSize = SizeSpec(matchWorkspace = true)
-                ),
-                FolderSpec(
-                    maxAvailableSize = 9999.dpToPx(),
-                    specType = SpecType.WIDTH,
-                    startPadding = sizeSpec16,
-                    endPadding = sizeSpec16,
-                    gutter = sizeSpec16,
-                    cellSize = SizeSpec(102f.dpToPx())
-                )
-            )
-
-        val heightSpecsExpected =
-            FolderSpec(
-                maxAvailableSize = 9999.dpToPx(),
-                specType = SpecType.HEIGHT,
-                startPadding = SizeSpec(24f.dpToPx()),
-                endPadding = SizeSpec(64f.dpToPx()),
-                gutter = sizeSpec16,
-                cellSize = SizeSpec(matchWorkspace = true)
-            )
-
-        assertThat(folderSpecs.widthSpecs.size).isEqualTo(widthSpecsExpected.size)
-        assertThat(folderSpecs.widthSpecs[0]).isEqualTo(widthSpecsExpected[0])
-        assertThat(folderSpecs.widthSpecs[1]).isEqualTo(widthSpecsExpected[1])
-
-        assertThat(folderSpecs.heightSpecs.size).isEqualTo(1)
-        assertThat(folderSpecs.heightSpecs[0]).isEqualTo(heightSpecsExpected)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun parseInvalidFile_missingTag_throwsError() {
-        val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_1)
-        FolderSpecs.create(resourceHelper)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
-        val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_2)
-        FolderSpecs.create(resourceHelper)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun parseInvalidFile_valueBiggerThan1_throwsError() {
-        val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_3)
-        FolderSpecs.create(resourceHelper)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun parseInvalidFile_missingSpecs_throwsError() {
-        val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_4)
-        FolderSpecs.create(resourceHelper)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun parseInvalidFile_missingWidthBreakpoint_throwsError() {
-        val availableSpace = 900.dpToPx()
-        val cells = 3
-
-        val workspaceSpec =
-            WorkspaceSpec(
-                maxAvailableSize = availableSpace,
-                specType = SpecType.WIDTH,
-                startPadding = SizeSpec(fixedSize = 10f),
-                endPadding = SizeSpec(fixedSize = 10f),
-                gutter = SizeSpec(fixedSize = 10f),
-                cellSize = SizeSpec(fixedSize = 10f)
-            )
-        val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
-
-        val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_5)
-        val folderSpecs = FolderSpecs.create(resourceHelper)
-        folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun parseInvalidFile_missingHeightBreakpoint_throwsError() {
-        val availableSpace = 900.dpToPx()
-        val cells = 3
-
-        val workspaceSpec =
-            WorkspaceSpec(
-                maxAvailableSize = availableSpace,
-                specType = SpecType.HEIGHT,
-                startPadding = SizeSpec(fixedSize = 10f),
-                endPadding = SizeSpec(fixedSize = 10f),
-                gutter = SizeSpec(fixedSize = 10f),
-                cellSize = SizeSpec(fixedSize = 10f)
-            )
-        val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
-
-        val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_5)
-        val folderSpecs = FolderSpecs.create(resourceHelper)
-        folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
-    }
-
-    @Test
-    fun retrievesCalculatedWidthSpec() {
-        val availableSpace = 800.dpToPx()
-        val cells = 3
-
-        val workspaceSpec =
-            WorkspaceSpec(
-                maxAvailableSize = availableSpace,
-                specType = SpecType.WIDTH,
-                startPadding = SizeSpec(fixedSize = 10f),
-                endPadding = SizeSpec(fixedSize = 10f),
-                gutter = SizeSpec(fixedSize = 10f),
-                cellSize = SizeSpec(fixedSize = 10f)
-            )
-        val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
-
-        val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
-        val folderSpecs = FolderSpecs.create(resourceHelper)
-        val calculatedWidthSpec =
-            folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
-
-        assertThat(calculatedWidthSpec.cells).isEqualTo(cells)
-        assertThat(calculatedWidthSpec.availableSpace).isEqualTo(availableSpace)
-        assertThat(calculatedWidthSpec.startPaddingPx).isEqualTo(16.dpToPx())
-        assertThat(calculatedWidthSpec.endPaddingPx).isEqualTo(16.dpToPx())
-        assertThat(calculatedWidthSpec.gutterPx).isEqualTo(16.dpToPx())
-        assertThat(calculatedWidthSpec.cellSizePx).isEqualTo(calculatedWorkspaceSpec.cellSizePx)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun retrievesCalculatedWidthSpec_invalidCalculatedWorkspaceSpecType_throwsError() {
-        val availableSpace = 10.dpToPx()
-        val cells = 3
-
-        val workspaceSpec =
-            WorkspaceSpec(
-                maxAvailableSize = availableSpace,
-                specType = SpecType.HEIGHT,
-                startPadding = SizeSpec(fixedSize = 10f),
-                endPadding = SizeSpec(fixedSize = 10f),
-                gutter = SizeSpec(fixedSize = 10f),
-                cellSize = SizeSpec(fixedSize = 10f)
-            )
-        val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
-
-        val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
-        val folderSpecs = FolderSpecs.create(resourceHelper)
-        folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
-    }
-
-    @Test
-    fun retrievesCalculatedHeightSpec() {
-        val availableSpace = 700.dpToPx()
-        val cells = 3
-
-        val workspaceSpec =
-            WorkspaceSpec(
-                maxAvailableSize = availableSpace,
-                specType = SpecType.HEIGHT,
-                startPadding = SizeSpec(fixedSize = 10f),
-                endPadding = SizeSpec(fixedSize = 10f),
-                gutter = SizeSpec(fixedSize = 10f),
-                cellSize = SizeSpec(fixedSize = 10f)
-            )
-        val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
-
-        val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
-        val folderSpecs = FolderSpecs.create(resourceHelper)
-        val calculatedHeightSpec =
-            folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
-
-        assertThat(calculatedHeightSpec.cells).isEqualTo(cells)
-        assertThat(calculatedHeightSpec.availableSpace).isEqualTo(availableSpace)
-        assertThat(calculatedHeightSpec.startPaddingPx).isEqualTo(24.dpToPx())
-        assertThat(calculatedHeightSpec.endPaddingPx).isEqualTo(64.dpToPx())
-        assertThat(calculatedHeightSpec.gutterPx).isEqualTo(16.dpToPx())
-        assertThat(calculatedHeightSpec.cellSizePx).isEqualTo(calculatedWorkspaceSpec.cellSizePx)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun retrievesCalculatedHeightSpec_invalidCalculatedWorkspaceSpecType_throwsError() {
-        val availableSpace = 10.dpToPx()
-        val cells = 3
-
-        val workspaceSpec =
-            WorkspaceSpec(
-                maxAvailableSize = availableSpace,
-                specType = SpecType.WIDTH,
-                startPadding = SizeSpec(fixedSize = 10f),
-                endPadding = SizeSpec(fixedSize = 10f),
-                gutter = SizeSpec(fixedSize = 10f),
-                cellSize = SizeSpec(fixedSize = 10f)
-            )
-        val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
-
-        val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
-        val folderSpecs = FolderSpecs.create(resourceHelper)
-        folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
-    }
-}
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt b/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
new file mode 100644
index 0000000..32249e0
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.responsive
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
+import com.android.launcher3.tests.R as TestR
+import com.android.launcher3.util.TestResourceHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class HotseatSpecsProviderTest : AbstractDeviceProfileTest() {
+    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+    val deviceSpec = deviceSpecs["phone"]!!
+    val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
+
+    @Before
+    fun setup() {
+        initializeVarsForPhone(deviceSpec)
+    }
+
+    @Test
+    fun parseValidFile() {
+        val hotseatSpecsProvider =
+            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+        val specs = hotseatSpecsProvider.getSpecsByAspectRatio(aspectRatio)
+
+        val expectedHeightSpecs =
+            listOf(
+                HotseatSpec(
+                    maxAvailableSize = 847.dpToPx(),
+                    dimensionType = ResponsiveSpec.DimensionType.HEIGHT,
+                    specType = ResponsiveSpecType.Hotseat,
+                    hotseatQsbSpace = SizeSpec(24f.dpToPx()),
+                    edgePadding = SizeSpec(48f.dpToPx())
+                ),
+                HotseatSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = ResponsiveSpec.DimensionType.HEIGHT,
+                    specType = ResponsiveSpecType.Hotseat,
+                    hotseatQsbSpace = SizeSpec(36f.dpToPx()),
+                    edgePadding = SizeSpec(48f.dpToPx())
+                ),
+            )
+
+        assertThat(specs.heightSpecs.size).isEqualTo(expectedHeightSpecs.size)
+        assertThat(specs.heightSpecs[0]).isEqualTo(expectedHeightSpecs[0])
+        assertThat(specs.heightSpecs[1]).isEqualTo(expectedHeightSpecs[1])
+
+        assertThat(specs.widthSpecs.size).isEqualTo(0)
+    }
+
+    @Test
+    fun parseValidLandscapeFile() {
+        val hotseatSpecsProvider =
+            HotseatSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_hotseat_land_file)
+            )
+        val specs = hotseatSpecsProvider.getSpecsByAspectRatio(aspectRatio)
+        assertThat(specs.heightSpecs.size).isEqualTo(0)
+
+        val expectedWidthSpecs =
+            listOf(
+                HotseatSpec(
+                    maxAvailableSize = 743.dpToPx(),
+                    dimensionType = ResponsiveSpec.DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Hotseat,
+                    hotseatQsbSpace = SizeSpec(0f),
+                    edgePadding = SizeSpec(48f.dpToPx())
+                ),
+                HotseatSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = ResponsiveSpec.DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Hotseat,
+                    hotseatQsbSpace = SizeSpec(0f),
+                    edgePadding = SizeSpec(64f.dpToPx())
+                ),
+            )
+
+        assertThat(specs.widthSpecs.size).isEqualTo(expectedWidthSpecs.size)
+        assertThat(specs.widthSpecs[0]).isEqualTo(expectedWidthSpecs[0])
+        assertThat(specs.widthSpecs[1]).isEqualTo(expectedWidthSpecs[1])
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_spaceIsNotFixedSize_throwsError() {
+        HotseatSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_1)
+        )
+    }
+}
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt b/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt
deleted file mode 100644
index f650e91..0000000
--- a/tests/src/com/android/launcher3/responsive/HotseatSpecsTest.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.responsive
-
-import android.content.Context
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.launcher3.AbstractDeviceProfileTest
-import com.android.launcher3.tests.R as TestR
-import com.android.launcher3.util.TestResourceHelper
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class HotseatSpecsTest : AbstractDeviceProfileTest() {
-    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
-
-    @Before
-    fun setup() {
-        initializeVarsForPhone(deviceSpecs["phone"]!!)
-    }
-
-    @Test
-    fun parseValidFile() {
-        val hotseatSpecs =
-            HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_file))
-
-        val expectedHeightSpecs =
-            listOf(
-                HotseatSpec(
-                    maxAvailableSize = 847.dpToPx(),
-                    specType = ResponsiveSpec.SpecType.HEIGHT,
-                    hotseatQsbSpace = SizeSpec(24f.dpToPx()),
-                    edgePadding = SizeSpec(48f.dpToPx())
-                ),
-                HotseatSpec(
-                    maxAvailableSize = 9999.dpToPx(),
-                    specType = ResponsiveSpec.SpecType.HEIGHT,
-                    hotseatQsbSpace = SizeSpec(36f.dpToPx()),
-                    edgePadding = SizeSpec(48f.dpToPx())
-                ),
-            )
-
-        assertThat(hotseatSpecs.heightSpecs.size).isEqualTo(expectedHeightSpecs.size)
-        assertThat(hotseatSpecs.heightSpecs[0]).isEqualTo(expectedHeightSpecs[0])
-        assertThat(hotseatSpecs.heightSpecs[1]).isEqualTo(expectedHeightSpecs[1])
-
-        assertThat(hotseatSpecs.widthSpecs.size).isEqualTo(0)
-    }
-
-    @Test
-    fun parseValidLandscapeFile() {
-        val hotseatSpecs =
-            HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_hotseat_land_file))
-        assertThat(hotseatSpecs.heightSpecs.size).isEqualTo(0)
-
-        val expectedWidthSpecs =
-            listOf(
-                HotseatSpec(
-                    maxAvailableSize = 743.dpToPx(),
-                    specType = ResponsiveSpec.SpecType.WIDTH,
-                    hotseatQsbSpace = SizeSpec(0f),
-                    edgePadding = SizeSpec(48f.dpToPx())
-                ),
-                HotseatSpec(
-                    maxAvailableSize = 9999.dpToPx(),
-                    specType = ResponsiveSpec.SpecType.WIDTH,
-                    hotseatQsbSpace = SizeSpec(0f),
-                    edgePadding = SizeSpec(64f.dpToPx())
-                ),
-            )
-
-        assertThat(hotseatSpecs.widthSpecs.size).isEqualTo(expectedWidthSpecs.size)
-        assertThat(hotseatSpecs.widthSpecs[0]).isEqualTo(expectedWidthSpecs[0])
-        assertThat(hotseatSpecs.widthSpecs[1]).isEqualTo(expectedWidthSpecs[1])
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun parseInvalidFile_spaceIsNotFixedSize_throwsError() {
-        HotseatSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_hotseat_file_case_1))
-    }
-}
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt b/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
new file mode 100644
index 0000000..7af0823
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
@@ -0,0 +1,262 @@
+/*
+ * 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.responsive
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
+import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
+import com.android.launcher3.tests.R
+import com.android.launcher3.util.TestResourceHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ResponsiveSpecsProviderTest : AbstractDeviceProfileTest() {
+    override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+    val deviceSpec = deviceSpecs["tablet"]!!
+    var aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
+
+    @Before
+    fun setup() {
+        initializeVarsForPhone(deviceSpec)
+    }
+
+    @Test
+    fun parseValidFile() {
+        val resourceHelper = TestResourceHelper(context, R.xml.valid_responsive_spec_unsorted)
+        val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
+
+        // Validate Portrait
+        val aspectRatioPortrait = 1.0f
+        val portraitSpecs = provider.getSpecsByAspectRatio(aspectRatioPortrait)
+
+        val (expectedPortWidthSpecs, expectedPortHeightSpecs) = expectedPortraitSpecs()
+        validateSpecs(
+            portraitSpecs,
+            aspectRatioPortrait,
+            expectedPortWidthSpecs,
+            expectedPortHeightSpecs
+        )
+
+        // Validate Landscape
+        val aspectRatioLandscape = 1.051f
+        val landscapeSpecs = provider.getSpecsByAspectRatio(aspectRatioLandscape)
+
+        val (expectedLandWidthSpecs, expectedLandHeightSpecs) = expectedLandscapeSpecs()
+        validateSpecs(
+            landscapeSpecs,
+            aspectRatioLandscape,
+            expectedLandWidthSpecs,
+            expectedLandHeightSpecs
+        )
+
+        // Validate Extra Spec
+        val aspectRatioExtra = 10.1f
+        val extraSpecs = provider.getSpecsByAspectRatio(aspectRatioExtra)
+
+        val expectedOtherWidthSpecs =
+            listOf(
+                ResponsiveSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(1f.dpToPx()),
+                    endPadding = SizeSpec(1f.dpToPx()),
+                    gutter = SizeSpec(8f.dpToPx()),
+                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                )
+            )
+
+        val expectedOtherHeightSpecs =
+            listOf(
+                ResponsiveSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = DimensionType.HEIGHT,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(2f.dpToPx()),
+                    endPadding = SizeSpec(2f.dpToPx()),
+                    gutter = SizeSpec(8f.dpToPx()),
+                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                ),
+            )
+
+        validateSpecs(
+            extraSpecs,
+            aspectRatioExtra,
+            expectedOtherWidthSpecs,
+            expectedOtherHeightSpecs
+        )
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseValidFile_invalidAspectRatio_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.valid_responsive_spec_unsorted)
+        val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
+        provider.getSpecsByAspectRatio(0f)
+    }
+
+    @Test(expected = InvalidResponsiveGridSpec::class)
+    fun parseInvalidFile_missingGroups_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_1)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
+    }
+
+    @Test(expected = InvalidResponsiveGridSpec::class)
+    fun parseInvalidFile_partialGroups_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_2)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun parseInvalidFile_invalidAspectRatio_throwsError() {
+        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_3)
+        ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
+    }
+
+    private fun validateSpecs(
+        specs: ResponsiveSpecGroup<ResponsiveSpec>,
+        expectedAspectRatio: Float,
+        expectedWidthSpecs: List<ResponsiveSpec>,
+        expectedHeightSpecs: List<ResponsiveSpec>
+    ) {
+        assertThat(specs.aspectRatio).isAtLeast(expectedAspectRatio)
+
+        assertThat(specs.widthSpecs.size).isEqualTo(expectedWidthSpecs.size)
+        specs.widthSpecs.forEachIndexed { index, responsiveSpec ->
+            assertThat(responsiveSpec).isEqualTo(expectedWidthSpecs[index])
+        }
+
+        assertThat(specs.heightSpecs.size).isEqualTo(expectedHeightSpecs.size)
+        specs.heightSpecs.forEachIndexed { index, responsiveSpec ->
+            assertThat(responsiveSpec).isEqualTo(expectedHeightSpecs[index])
+        }
+    }
+
+    private fun expectedPortraitSpecs(): Pair<List<ResponsiveSpec>, List<ResponsiveSpec>> {
+        val sizeSpec16 = SizeSpec(16f.dpToPx())
+        val expectedWidthSpecs =
+            listOf(
+                ResponsiveSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(22f.dpToPx()),
+                    endPadding = SizeSpec(22f.dpToPx()),
+                    gutter = sizeSpec16,
+                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                )
+            )
+
+        val expectedHeightSpecs =
+            listOf(
+                ResponsiveSpec(
+                    maxAvailableSize = 584.dpToPx(),
+                    dimensionType = DimensionType.HEIGHT,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(0f),
+                    endPadding = SizeSpec(32f.dpToPx()),
+                    gutter = sizeSpec16,
+                    cellSize = SizeSpec(ofAvailableSpace = .15808f)
+                ),
+                ResponsiveSpec(
+                    maxAvailableSize = 612.dpToPx(),
+                    dimensionType = DimensionType.HEIGHT,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(0f),
+                    endPadding = SizeSpec(ofRemainderSpace = 1f),
+                    gutter = sizeSpec16,
+                    cellSize = SizeSpec(104f.dpToPx())
+                ),
+                ResponsiveSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = DimensionType.HEIGHT,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(8f.dpToPx()),
+                    endPadding = SizeSpec(ofRemainderSpace = 1f),
+                    gutter = sizeSpec16,
+                    cellSize = SizeSpec(104f.dpToPx())
+                ),
+            )
+
+        return Pair(expectedWidthSpecs, expectedHeightSpecs)
+    }
+
+    private fun expectedLandscapeSpecs(): Pair<List<ResponsiveSpec>, List<ResponsiveSpec>> {
+        val sizeSpec12 = SizeSpec(12f.dpToPx())
+        val expectedWidthSpecs =
+            listOf(
+                ResponsiveSpec(
+                    maxAvailableSize = 602.dpToPx(),
+                    dimensionType = DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(0f.dpToPx()),
+                    endPadding = SizeSpec(36f.dpToPx()),
+                    gutter = sizeSpec12,
+                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                ),
+                ResponsiveSpec(
+                    maxAvailableSize = 716.dpToPx(),
+                    dimensionType = DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(16f.dpToPx()),
+                    endPadding = SizeSpec(64f.dpToPx()),
+                    gutter = sizeSpec12,
+                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                ),
+                ResponsiveSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = DimensionType.WIDTH,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(36f.dpToPx()),
+                    endPadding = SizeSpec(80f.dpToPx()),
+                    gutter = sizeSpec12,
+                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                )
+            )
+
+        val expectedHeightSpecs =
+            listOf(
+                ResponsiveSpec(
+                    maxAvailableSize = 371.dpToPx(),
+                    dimensionType = DimensionType.HEIGHT,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(0f),
+                    endPadding = SizeSpec(24f.dpToPx()),
+                    gutter = sizeSpec12,
+                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                ),
+                ResponsiveSpec(
+                    maxAvailableSize = 9999.dpToPx(),
+                    dimensionType = DimensionType.HEIGHT,
+                    specType = ResponsiveSpecType.Workspace,
+                    startPadding = SizeSpec(0f),
+                    endPadding = SizeSpec(34f.dpToPx()),
+                    gutter = sizeSpec12,
+                    cellSize = SizeSpec(ofRemainderSpace = .25f)
+                ),
+            )
+
+        return Pair(expectedWidthSpecs, expectedHeightSpecs)
+    }
+}
diff --git a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
index 0364069..9781645 100644
--- a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
@@ -32,22 +33,30 @@
 @RunWith(AndroidJUnit4::class)
 class WorkspaceSpecsTest : AbstractDeviceProfileTest() {
     override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+    val deviceSpec = deviceSpecs["phone"]!!
+    val aspectRatio = deviceSpec.naturalSize.first.toFloat() / deviceSpec.naturalSize.second
 
     @Before
     fun setup() {
-        initializeVarsForPhone(deviceSpecs["phone"]!!)
+        initializeVarsForPhone(deviceSpec)
     }
 
     @Test
     fun parseValidFile() {
         val workspaceSpecs =
-            WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
-        assertThat(workspaceSpecs.heightSpecs.size).isEqualTo(3)
-        assertThat(workspaceSpecs.heightSpecs[0].toString())
+            ResponsiveSpecsProvider.create(
+                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                ResponsiveSpecType.Workspace
+            )
+
+        val specs = workspaceSpecs.getSpecsByAspectRatio(aspectRatio)
+        assertThat(specs.heightSpecs.size).isEqualTo(3)
+        assertThat(specs.heightSpecs[0].toString())
             .isEqualTo(
-                "WorkspaceSpec(" +
+                "ResponsiveSpec(" +
                     "maxAvailableSize=1533, " +
-                    "specType=HEIGHT, " +
+                    "dimensionType=HEIGHT, " +
+                    "specType=Workspace, " +
                     "startPadding=SizeSpec(fixedSize=0.0, " +
                     "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0, " +
@@ -70,11 +79,12 @@
                     "maxSize=2147483647)" +
                     ")"
             )
-        assertThat(workspaceSpecs.heightSpecs[1].toString())
+        assertThat(specs.heightSpecs[1].toString())
             .isEqualTo(
-                "WorkspaceSpec(" +
+                "ResponsiveSpec(" +
                     "maxAvailableSize=1607, " +
-                    "specType=HEIGHT, " +
+                    "dimensionType=HEIGHT, " +
+                    "specType=Workspace, " +
                     "startPadding=SizeSpec(fixedSize=0.0, " +
                     "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0, " +
@@ -97,11 +107,12 @@
                     "maxSize=2147483647)" +
                     ")"
             )
-        assertThat(workspaceSpecs.heightSpecs[2].toString())
+        assertThat(specs.heightSpecs[2].toString())
             .isEqualTo(
-                "WorkspaceSpec(" +
+                "ResponsiveSpec(" +
                     "maxAvailableSize=26247, " +
-                    "specType=HEIGHT, " +
+                    "dimensionType=HEIGHT, " +
+                    "specType=Workspace, " +
                     "startPadding=SizeSpec(fixedSize=21.0, " +
                     "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0, " +
@@ -124,12 +135,13 @@
                     "maxSize=2147483647)" +
                     ")"
             )
-        assertThat(workspaceSpecs.widthSpecs.size).isEqualTo(1)
-        assertThat(workspaceSpecs.widthSpecs[0].toString())
+        assertThat(specs.widthSpecs.size).isEqualTo(1)
+        assertThat(specs.widthSpecs[0].toString())
             .isEqualTo(
-                "WorkspaceSpec(" +
+                "ResponsiveSpec(" +
                     "maxAvailableSize=26247, " +
-                    "specType=WIDTH, " +
+                    "dimensionType=WIDTH, " +
+                    "specType=Workspace, " +
                     "startPadding=SizeSpec(fixedSize=58.0, " +
                     "ofAvailableSpace=0.0, " +
                     "ofRemainderSpace=0.0, " +
@@ -156,29 +168,33 @@
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingTag_throwsError() {
-        WorkspaceSpecs.create(
-            TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_1)
+        ResponsiveSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_1),
+            ResponsiveSpecType.Workspace
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
-        WorkspaceSpecs.create(
-            TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_2)
+        ResponsiveSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_2),
+            ResponsiveSpecType.Workspace
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_valueBiggerThan1_throwsError() {
-        WorkspaceSpecs.create(
-            TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_3)
+        ResponsiveSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_3),
+            ResponsiveSpecType.Workspace
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_matchWorkspace_true_throwsError() {
-        WorkspaceSpecs.create(
-            TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_4)
+        ResponsiveSpecsProvider.create(
+            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_4),
+            ResponsiveSpecType.Workspace
         )
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index ed846ed..8847b8b 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -45,6 +45,7 @@
 import android.system.OsConstants;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
@@ -91,7 +92,7 @@
 import java.util.function.Supplier;
 
 /**
- * Base class for all instrumentation tests providing various utility methods. RUN 4
+ * Base class for all instrumentation tests providing various utility methods.
  */
 public abstract class AbstractLauncherUiTest {
 
@@ -108,7 +109,7 @@
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
-    protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
+    protected final UiDevice mDevice = getUiDevice();
     protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
     protected Context mTargetContext;
     protected String mTargetPackage;
@@ -160,7 +161,7 @@
                 if (TestHelpers.isInLauncherProcess()) {
                     Debug.dumpHprofData(fileName);
                 } else {
-                    final UiDevice device = UiDevice.getInstance(getInstrumentation());
+                    final UiDevice device = getUiDevice();
                     device.executeShellCommand(
                             "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
                 }
@@ -187,7 +188,7 @@
             Utilities.enableRunningInTestHarnessForTests();
             mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
                             TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString())
-                            .getString("result"));
+                    .getString("result"));
             mLauncher.setOnSettledStateAction(
                     containerType -> executeOnLauncher(
                             launcher ->
@@ -248,11 +249,7 @@
     public void setUp() throws Exception {
         mLauncher.onTestStart();
 
-        if (TestStabilityRule.isPresubmit()) {
-            aggressivelyUnlockSysUi();
-        } else {
-            verifyKeyguardInvisible();
-        }
+        waitForSetupWizardDismissal();
 
         final String launcherPackageName = mDevice.getLauncherPackageName();
         try {
@@ -284,23 +281,38 @@
             }
         }
 
-        verifyKeyguardInvisible();
+        onTestStart();
     }
 
-    private boolean hasSystemUiObject(String resId) {
-        return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId));
+    /** Method that should be called when a test starts. */
+    public static void onTestStart() {
+        if (TestStabilityRule.isPresubmit()) {
+            aggressivelyUnlockSysUi();
+        } else {
+            verifyKeyguardInvisible();
+        }
     }
 
-    // Seeing if this will decrease: b/303755862
-    void aggressivelyUnlockSysUi() {
+    private static boolean hasSystemUiObject(String resId) {
+        return getUiDevice().hasObject(
+                By.res(SYSTEMUI_PACKAGE, resId));
+    }
+
+    @NonNull
+    private static UiDevice getUiDevice() {
+        return UiDevice.getInstance(getInstrumentation());
+    }
+
+    private static void aggressivelyUnlockSysUi() {
+        final UiDevice device = getUiDevice();
         for (int i = 0; i < 10 && hasSystemUiObject("keyguard_status_view"); ++i) {
             Log.d(TAG, "Before attempting to unlock the phone");
             try {
-                mDevice.executeShellCommand("input keyevent 82");
+                device.executeShellCommand("input keyevent 82");
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
-            mDevice.waitForIdle();
+            device.waitForIdle();
         }
         Assert.assertTrue("Keyguard still visible",
                 TestHelpers.wait(
@@ -308,7 +320,37 @@
         Log.d(TAG, "Keyguard is not visible");
     }
 
-    public static void verifyKeyguardInvisible() {
+    // b/309008042
+    private static boolean sFirstTimeWaitingForWizard = true;
+
+    // b/309008042
+    static {
+        waitForSetupWizardDismissal();
+    }
+
+    // b/309008042
+    // TODO(309471958) Productize killing/dismissal of setup wizard.
+    /** Waits for setup wizard to go away. */
+    public static void waitForSetupWizardDismissal() {
+        if (!TestStabilityRule.isPresubmit()) return;
+
+        if (sFirstTimeWaitingForWizard) {
+            try {
+                getUiDevice().executeShellCommand(
+                        "am force-stop com.google.android.setupwizard");
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        final boolean wizardDismissed = TestHelpers.wait(
+                Until.gone(By.pkg("com.google.android.setupwizard").depth(0)),
+                sFirstTimeWaitingForWizard ? 120000 : 0);
+        sFirstTimeWaitingForWizard = false;
+        Assert.assertTrue("Setup wizard is still visible", wizardDismissed);
+    }
+
+    private static void verifyKeyguardInvisible() {
         final boolean keyguardAlreadyVisible = sSeenKeyguard;
 
         sSeenKeyguard = sSeenKeyguard
@@ -422,6 +464,7 @@
     // flakiness.
     protected void waitForLauncherCondition(
             String message, Function<Launcher, Boolean> condition, long timeout) {
+        waitForSetupWizardDismissal();
         verifyKeyguardInvisible();
         if (!TestHelpers.isInLauncherProcess()) return;
         Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher);
diff --git a/tests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java b/tests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
index ba416ae..3411fc1 100644
--- a/tests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
+++ b/tests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
@@ -18,14 +18,19 @@
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.when;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -34,15 +39,20 @@
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.UserIconInfo;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Arrays;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ActivityAllAppsContainerViewTest {
@@ -50,19 +60,38 @@
     private static final UserHandle WORK_HANDLE = new UserHandle(13);
     @Mock
     private StatsLogManager mStatsLogManager;
+    @Mock
+    private UserCache mUserCache;
+    @Mock
+    private UserManager mUserManager;
     private AppInfo[] mWorkAppInfo;
     private ActivityAllAppsContainerView<?> mActivityAllAppsContainerView;
     private WorkProfileManager mWorkManager;
+    private Context mContext;
+
+    @Rule public final SetFlagsRule mSetFlagsRule = getFlagsRule();
+
+    private SetFlagsRule getFlagsRule() {
+        SetFlagsRule flagsRule = new SetFlagsRule();
+        flagsRule.initAllFlagsToReleaseConfigDefault();
+        return flagsRule;
+    }
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Context context = new ActivityContextWrapper(getApplicationContext());
-        mActivityAllAppsContainerView = new ActivityAllAppsContainerView(context);
-        mWorkManager = new WorkProfileManager(context.getSystemService(UserManager.class),
-                mActivityAllAppsContainerView, mStatsLogManager);
+        mContext = new ActivityContextWrapper(getApplicationContext());
+        mActivityAllAppsContainerView = new ActivityAllAppsContainerView(mContext);
+        when(mUserCache.getUserProfiles())
+                .thenReturn(Arrays.asList(Process.myUserHandle(), WORK_HANDLE));
+        when(mUserCache.getUserInfo(Process.myUserHandle()))
+                .thenReturn(new UserIconInfo(Process.myUserHandle(), 0));
+        when(mUserCache.getUserInfo(WORK_HANDLE))
+                .thenReturn(new UserIconInfo(WORK_HANDLE, 1));
+        mWorkManager = new WorkProfileManager(mUserManager, mActivityAllAppsContainerView,
+                mStatsLogManager, mUserCache);
         mActivityAllAppsContainerView.setWorkManager(mWorkManager);
-        ComponentName componentName = new ComponentName(context,
+        ComponentName componentName = new ComponentName(mContext,
                 "com.android.launcher3.tests.Activity" + "Gmail");
         AppInfo gmailWorkAppInfo = new AppInfo(componentName, "Gmail", WORK_HANDLE, new Intent());
         mWorkAppInfo = new AppInfo[]{gmailWorkAppInfo};
@@ -85,4 +114,22 @@
 
         assertThat(mActivityAllAppsContainerView.shouldShowTabs()).isEqualTo(true);
     }
+
+    @Test
+    public void testWorkProfileEnabled_requestQuietModeCalledCorrectly() throws Exception {
+        /* Setup */
+        when(mUserManager.requestQuietModeEnabled(false, WORK_HANDLE))
+                .thenReturn(true);
+
+        /* Execution */
+        mWorkManager.setWorkProfileEnabled(true);
+
+        /* Assertion */
+        awaitTasksCompleted();
+        Mockito.verify(mUserManager).requestQuietModeEnabled(false, WORK_HANDLE);
+    }
+
+    private static void awaitTasksCompleted() throws Exception {
+        UI_HELPER_EXECUTOR.submit(() -> null).get();
+    }
 }
diff --git a/tests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
index cf80ece..b4d3ba8 100644
--- a/tests/src/com/android/launcher3/util/TestResourceHelper.kt
+++ b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
@@ -32,6 +32,8 @@
                 styleId.contentEquals(R.styleable.WorkspaceSpec) -> TestR.styleable.WorkspaceSpec
                 styleId.contentEquals(R.styleable.FolderSpec) -> TestR.styleable.FolderSpec
                 styleId.contentEquals(R.styleable.AllAppsSpec) -> TestR.styleable.AllAppsSpec
+                styleId.contentEquals(R.styleable.ResponsiveSpecGroup) ->
+                    TestR.styleable.ResponsiveSpecGroup
                 else -> styleId.clone()
             }
 
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 62d70ad..10b428a 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -59,8 +59,9 @@
                         throw new AssertionError(
                                 "Launcher received events not sent by the test. This may mean "
                                         + "that the touch screen of the lab device has sent false"
-                                        + " events. See the logcat for TaplEvents tag and look "
-                                        + "for events with deviceId != -1");
+                                        + " events. See the logcat for "
+                                        + "TaplEvents|LauncherEvents|TaplTarget tag and look for "
+                                        + "events with deviceId != -1");
                     }
                 }
             }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index a85b6bd..770fe14 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -25,6 +25,7 @@
 import androidx.test.uiautomator.UiObject2;
 
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -201,7 +202,8 @@
             OverviewTask task = getCurrentTask();
             mLauncher.assertNotNull("current task is null", task);
             mLauncher.scrollLeftByDistance(verifyActiveContainer(),
-                    task.getVisibleWidth() + mLauncher.getOverviewPageSpacing());
+                    mLauncher.getRealDisplaySize().x - task.getUiObject().getVisibleBounds().left
+                            + mLauncher.getOverviewPageSpacing());
 
             try (LauncherInstrumentation.Closable c2 =
                          mLauncher.addContextLayer("scrolled task off screen")) {
@@ -231,14 +233,14 @@
         final List<UiObject2> taskViews = getTasks();
         mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
 
-        // taskViews contains up to 3 task views: the 'main' (having the widest visible part) one
-        // in the center, and parts of its right and left siblings. Find the main task view by
-        // its width.
-        final UiObject2 widestTask = Collections.max(taskViews,
-                (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(),
-                        mLauncher.getVisibleBounds(t2).width()));
-
-        return new OverviewTask(mLauncher, widestTask, this);
+        final List<OverviewTask> overviewTasks = taskViews.stream().map(
+                task -> new OverviewTask(mLauncher, task, this)).toList();
+        // The widest, and most top-right task should be the current task
+        return Collections.max(overviewTasks,
+                Comparator.comparingInt(OverviewTask::getVisibleWidth)
+                        .thenComparingInt(OverviewTask::getTaskCenterX)
+                        .thenComparing(
+                                Comparator.comparing(OverviewTask::getTaskCenterY).reversed()));
     }
 
     /** Returns an overview task matching TestActivity {@param activityNumber}. */
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 307f192..e30885b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -564,6 +564,9 @@
         if (hasSystemLauncherObject(OVERVIEW_RES_ID)) return "Overview";
         if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
         if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
+        if (mDevice.hasObject(By.pkg(getLauncherPackageName()).depth(0))) {
+            return "<Launcher in invalid state>";
+        }
         return "LaunchedApp (" + getVisiblePackages() + ")";
     }
 
@@ -1608,8 +1611,11 @@
         scroll(
                 container,
                 Direction.LEFT,
-                new Rect(leftGestureMargin, 0,
-                        containerRect.width() - distance - rightGestureMarginInContainer, 0),
+                new Rect(leftGestureMargin,
+                        0,
+                        Math.max(containerRect.width() - distance - leftGestureMargin,
+                                rightGestureMarginInContainer),
+                        0),
                 10,
                 true);
     }
@@ -1782,7 +1788,7 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
-    boolean isGridOnlyOverviewEnabled() {
+    public boolean isGridOnlyOverviewEnabled() {
         return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW).getBoolean(
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
@@ -2060,6 +2066,7 @@
     }
 
     // TODO(b/270393900): Remove with ENABLE_ALL_APPS_SEARCH_IN_TASKBAR flag cleanup.
+
     /** Refreshes the known overview target in TIS. */
     public void refreshOverviewTarget() {
         getTestInfo(TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET);
@@ -2225,7 +2232,7 @@
             int bottomBound = Math.min(
                     containerBounds.bottom,
                     getRealDisplaySize().y - getImeInsets().bottom);
-            int y = (bottomBound - containerBounds.top) / 2;
+            int y = (bottomBound + containerBounds.top) / 2;
             // Do not tap in the status bar.
             y = Math.max(y, getWindowInsets().top);
 
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 06fac48..8a34f0d 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -73,10 +73,13 @@
      * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider.
      */
     private int getCombinedSplitTaskHeight() {
-        UiObject2 taskSnapshot1 =
-                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_1);
-        UiObject2 taskSnapshot2 =
-                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_2);
+        UiObject2 taskSnapshot1 = findObjectInTask(TASK_SNAPSHOT_1);
+        UiObject2 taskSnapshot2 = findObjectInTask(TASK_SNAPSHOT_2);
+
+        // If the split task is partly off screen, taskSnapshot1 can be invisible.
+        if (taskSnapshot1 == null) {
+            return taskSnapshot2.getVisibleBounds().height();
+        }
 
         int top = Math.min(
                 taskSnapshot1.getVisibleBounds().top, taskSnapshot2.getVisibleBounds().top);
@@ -102,10 +105,8 @@
      * Calculates the visible width for split tasks, containing 2 snapshot tiles and a divider.
      */
     private int getCombinedSplitTaskWidth() {
-        UiObject2 taskSnapshot1 =
-                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_1);
-        UiObject2 taskSnapshot2 =
-                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_2);
+        UiObject2 taskSnapshot1 = findObjectInTask(TASK_SNAPSHOT_1);
+        UiObject2 taskSnapshot2 = findObjectInTask(TASK_SNAPSHOT_2);
 
         int left = Math.min(
                 taskSnapshot1.getVisibleBounds().left, taskSnapshot2.getVisibleBounds().left);
@@ -116,11 +117,15 @@
     }
 
     int getTaskCenterX() {
-        return mTask.getVisibleCenter().x;
+        return mTask.getParent().getVisibleCenter().x;
+    }
+
+    int getTaskCenterY() {
+        return mTask.getParent().getVisibleCenter().y;
     }
 
     float getExactCenterX() {
-        return mTask.getVisibleBounds().exactCenterX();
+        return mTask.getParent().getVisibleBounds().exactCenterX();
     }
 
     UiObject2 getUiObject() {
@@ -144,7 +149,8 @@
 
             boolean taskWasFocused = mLauncher.isTablet() && getVisibleHeight() == mLauncher
                     .getFocusedTaskHeightForTablet();
-            List<Integer> originalTasksCenterX = getCurrentTasksCenterXList();
+            List<Integer> originalTasksCenterX =
+                    getCurrentTasksCenterXList().stream().sorted().toList();
             boolean isClearAllVisibleBeforeDismiss = mOverview.isClearAllVisible();
 
             dismissBySwipingUp();
@@ -155,7 +161,8 @@
                             mOverview.getFocusedTaskForTablet());
                 }
                 if (!isClearAllVisibleBeforeDismiss) {
-                    List<Integer> currentTasksCenterX = getCurrentTasksCenterXList();
+                    List<Integer> currentTasksCenterX =
+                            getCurrentTasksCenterXList().stream().sorted().toList();
                     if (originalTasksCenterX.size() == currentTasksCenterX.size()) {
                         // Check for the same number of visible tasks before and after to
                         // avoid asserting on cases of shifting all tasks to close the distance
@@ -173,7 +180,7 @@
         // Dismiss the task via flinging it up.
         final Rect taskBounds = mLauncher.getVisibleBounds(mTask);
         final int centerX = taskBounds.centerX();
-        final int centerY = taskBounds.centerY();
+        final int centerY = taskBounds.bottom - 1;
         mLauncher.executeAndWaitForLauncherEvent(
                 () -> mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false,
                         LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER),
@@ -253,6 +260,10 @@
     }
 
     boolean isTaskSplit() {
-        return mLauncher.findObjectInContainer(mTask.getParent(), "bottomright_snapshot") != null;
+        return findObjectInTask(TASK_SNAPSHOT_2) != null;
+    }
+
+    private UiObject2 findObjectInTask(String resName) {
+        return mTask.getParent().findObject(mLauncher.getOverviewObjectSelector(resName));
     }
 }