Merge "Revert "Revert "Remove TaplTestsQuickstep tests from PlatinumTes..."" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index ace2210..5689c8a 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -55,3 +55,17 @@
     description: "Enables launcher to listen to all transitions that include home activity"
     bug: "306053414"
 }
+
+flag {
+    name: "enable_taskbar_pinning"
+    namespace: "launcher"
+    description: "Enables taskbar pinning to allow user to switch between transient and persistent taskbar flavors."
+    bug: "270396583"
+}
+
+flag {
+    name: "enable_split_from_fullscreen_with_keyboard_shortcuts"
+    namespace: "launcher"
+    description: "Enables initiating split from a fullscreen app using keyboard shortcuts"
+    bug: "270394122"
+}
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index 87ccec5..fc79200 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -6,3 +6,10 @@
     description: "Enables all Launcher features associated with private space."
     bug: "306187906"
 }
+
+flag {
+    name: "private_space_animation"
+    namespace: "launcher_search"
+    description: "This flag enables the animation of the Private Space container"
+    bug: "299294792"
+}
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 0fda0bf..fe517fa 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -31,7 +31,7 @@
             android:layout_height="1dp"
             android:layout_weight="1" />
 
-        <Button
+        <com.android.quickstep.views.ScreenshotActionButton
             android:id="@+id/action_screenshot"
             style="@style/OverviewActionButton"
             android:layout_width="wrap_content"
@@ -40,17 +40,12 @@
             android:text="@string/action_screenshot"
             android:theme="@style/ThemeControlHighlightWorkspaceColor" />
 
-        <Space
-            android:id="@+id/action_split_space"
-            android:layout_width="@dimen/overview_actions_button_spacing"
-            android:layout_height="1dp"
-            android:visibility="gone" />
-
-        <Button
+        <com.android.quickstep.views.SplitActionButton
             android:id="@+id/action_split"
             style="@style/OverviewActionButton"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/overview_actions_button_spacing"
             android:text="@string/action_split"
             android:theme="@style/ThemeControlHighlightWorkspaceColor"
             android:visibility="gone" />
diff --git a/quickstep/res/layout/taskbar_divider_popup_menu.xml b/quickstep/res/layout/taskbar_divider_popup_menu.xml
index 6fbb586..7f4f76c 100644
--- a/quickstep/res/layout/taskbar_divider_popup_menu.xml
+++ b/quickstep/res/layout/taskbar_divider_popup_menu.xml
@@ -57,7 +57,7 @@
             android:paddingStart="12dp"
             android:layout_weight="1"
             android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
-            android:singleLine="true"
+            android:lines="1"
             android:ellipsize="end"
             android:textSize="14sp"
             android:textColor="?android:attr/textColorPrimary"
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index aa42f2f..ef885e8 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -115,7 +115,7 @@
     <string name="taskbar_button_a11y" msgid="5241161324875094465">"Erabilerraztasuna"</string>
     <string name="taskbar_button_back" msgid="8558862226461164514">"Atzera"</string>
     <string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IMEaren etengailua"</string>
-    <string name="taskbar_button_recents" msgid="7273376136216613134">"Azkenak"</string>
+    <string name="taskbar_button_recents" msgid="7273376136216613134">"Azkenaldikoak"</string>
     <string name="taskbar_button_notifications" msgid="7471740351507357318">"Jakinarazpenak"</string>
     <string name="taskbar_button_quick_settings" msgid="227662894293189391">"Ezarpen bizkorrak"</string>
     <string name="taskbar_a11y_title" msgid="6432169809852243110">"Zereginen barra"</string>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index dd9fc63..3dade66 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -235,6 +235,7 @@
     <string name="toast_split_app_unsupported">Choose another app to use split screen</string>
     <!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] -->
     <string name="blocked_by_policy">This action isn\'t allowed by the app or your organization</string>
+    <string name="split_widgets_not_supported">Widgets not currently supported, please select another app</string>
 
     <!-- ******* Skip tutorial dialog ******* -->
     <!-- Title for the dialog that allows the user to skip the gesture navigation tutorial. [CHAR_LIMIT=40] -->
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index be4426d..0ef4541 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.LauncherAnimUtils.ROTATION_DRAWABLE_PERCENT;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.taskbar.LauncherTaskbarUIController.SYSUI_SURFACE_PROGRESS_INDEX;
 import static com.android.launcher3.taskbar.TaskbarManager.isPhoneButtonNavMode;
 import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
@@ -734,7 +735,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 (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..820b996 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -30,12 +30,12 @@
 import static com.android.launcher3.Flags.enableCursorHoverStates;
 import static com.android.launcher3.Utilities.calculateTextHeight;
 import static com.android.launcher3.Utilities.isRunningInTestHarness;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 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.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);
@@ -871,7 +871,7 @@
         }
 
         boolean shouldTreatAsTransient = DisplayController.isTransientTaskbar(this)
-                || (ENABLE_TASKBAR_PINNING.get() && !isThreeButtonNav());
+                || (enableTaskbarPinning() && !isThreeButtonNav());
 
         int extraHeightForTaskbarTooltips = enableCursorHoverStates()
                 ? resources.getDimensionPixelSize(R.dimen.arrow_toast_arrow_height)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 1eb8d53..a24cf4b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -18,6 +18,8 @@
 import static android.view.KeyEvent.ACTION_UP;
 import static android.view.KeyEvent.KEYCODE_BACK;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
+
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.RectF;
@@ -126,7 +128,7 @@
     }
 
     protected void onDestroy() {
-        onDestroy(!TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW);
+        onDestroy(!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 0b52195..1a34b7a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -41,6 +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_NAVBAR_UNIFICATION
 import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.util.DisplayController
@@ -253,7 +254,7 @@
                                 visInsetsSizeForTappableElement
                         ),
                 )
-        if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        if ((context.isGestureNav || 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..c0b07e7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -23,6 +23,7 @@
 
 import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
 import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -41,7 +42,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 +71,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,9 +99,6 @@
             | 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);
-
     private static final Uri USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(
             Settings.Secure.USER_SETUP_COMPLETE);
 
@@ -198,7 +196,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 +249,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 +294,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 +426,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 +492,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 ENABLE_TASKBAR_NAVBAR_UNIFICATION && deviceProfile.isPhone;
     }
 
     /**
@@ -505,7 +504,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/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 5be74be..e5c4c96 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -23,7 +23,7 @@
 import static com.android.app.animation.Interpolators.INSTANT;
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_LONGPRESS_SHOW;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
@@ -333,7 +333,7 @@
         // that taskbar unstashes when going to 3 button mode (supportsVisualStashing() false).
         boolean isManuallyStashedInApp = supportsVisualStashing()
                 && !isTransientTaskbar
-                && !ENABLE_TASKBAR_PINNING.get()
+                && !enableTaskbarPinning()
                 && mPrefs.getBoolean(SHARED_PREFS_STASHED_KEY, DEFAULT_STASHED_PREF);
         boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
         updateStateForFlag(FLAG_STASHED_IN_APP_MANUAL, isManuallyStashedInApp);
@@ -365,7 +365,7 @@
      * Returns whether the user can manually stash the taskbar based on the current device state.
      */
     protected boolean supportsManualStashing() {
-        if (ENABLE_TASKBAR_PINNING.get() && LauncherPrefs.get(mActivity).get(TASKBAR_PINNING)) {
+        if (enableTaskbarPinning() && LauncherPrefs.get(mActivity).get(TASKBAR_PINNING)) {
             return false;
         }
         return supportsVisualStashing()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index a7461b7..81e4ad5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -20,6 +20,7 @@
 
 import static com.android.launcher3.Flags.enableCursorHoverStates;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 
 import android.content.Context;
@@ -45,7 +46,6 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.ThemedIconDrawable;
 import com.android.launcher3.model.data.FolderInfo;
@@ -128,7 +128,7 @@
 
         int actualMargin = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing);
         int actualIconSize = mActivityContext.getDeviceProfile().taskbarIconSize;
-        if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
+        if (enableTaskbarPinning()) {
             DeviceProfile deviceProfile = mActivityContext.getTransientTaskbarDeviceProfile();
             actualIconSize = deviceProfile.taskbarIconSize;
         }
@@ -155,12 +155,11 @@
                     .inflate(R.layout.taskbar_all_apps_button, this, false);
             mAllAppsButton.setIconDrawable(resources.getDrawable(
                     getAllAppsButton(isTransientTaskbar)));
-            mAllAppsButton.setScaleX(mIsRtl ? -1 : 1);
             mAllAppsButton.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
             mAllAppsButton.setForegroundTint(
                     mActivityContext.getColor(R.color.all_apps_button_color));
 
-            if (FeatureFlags.ENABLE_TASKBAR_PINNING.get()) {
+            if (enableTaskbarPinning()) {
                 mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
                         R.layout.taskbar_divider,
                         this, false);
@@ -177,7 +176,7 @@
     @DrawableRes
     private int getAllAppsButton(boolean isTransientTaskbar) {
         boolean shouldSelectTransientIcon =
-                (isTransientTaskbar || FeatureFlags.ENABLE_TASKBAR_PINNING.get())
+                (isTransientTaskbar || enableTaskbarPinning())
                 && !mActivityContext.isThreeButtonNav();
         if (ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
             return shouldSelectTransientIcon
@@ -546,7 +545,7 @@
         int iconLayoutBoundsWidth =
                 countExcludingQsb * (mItemMarginLeftRight * 2 + mIconTouchSize);
 
-        if (FeatureFlags.ENABLE_TASKBAR_PINNING.get() && countExcludingQsb > 1) {
+        if (enableTaskbarPinning() && countExcludingQsb > 1) {
             // We are removing 4 * mItemMarginLeftRight as there should be no space between
             // All Apps icon, divider icon, and first app icon in taskbar
             iconLayoutBoundsWidth -= mItemMarginLeftRight * 4;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 78d5bd3..0225de4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -25,7 +25,8 @@
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.anim.AnimatedFloat.VALUE;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
@@ -214,13 +215,13 @@
 
         mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
 
-        if (TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW) {
+        if (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)
                     .animateToValue(isPhoneButtonNavMode(mActivity) ? 0 : 1).start();
         }
-        if (ENABLE_TASKBAR_PINNING.get()) {
+        if (enableTaskbarPinning()) {
             mTaskbarView.addOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
         }
     }
@@ -233,7 +234,7 @@
     }
 
     public void onDestroy() {
-        if (ENABLE_TASKBAR_PINNING.get()) {
+        if (enableTaskbarPinning()) {
             mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
         }
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
@@ -366,6 +367,10 @@
         float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
                 persistentTaskbarAllAppsOffset);
 
+        if (mIsRtl) {
+            allAppIconTranslateRange *= -1;
+        }
+
         float halfIconCount = iconViews.length / 2.0f;
         for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
             View iconView = iconViews[iconIndex];
@@ -671,7 +676,7 @@
                 // to avoid icons disappearing rather than fading out visually.
                 setter.setViewAlpha(child, 0, Interpolators.clampToProgress(LINEAR, 0.8f, 1f));
             } else if ((isAllAppsButton && !FeatureFlags.ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.get())
-                    || (isTaskbarDividerView && ENABLE_TASKBAR_PINNING.get())) {
+                    || (isTaskbarDividerView && enableTaskbarPinning())) {
                 if (!isToHome
                         && mIsHotseatIconOnTopWhenAligned
                         && mIsStashed) {
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/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index f5ba8f9..8092582 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -27,8 +27,10 @@
 import android.util.Pair;
 import android.view.View;
 import android.widget.RemoteViews;
+import android.widget.Toast;
 import android.window.SplashScreen;
 
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
@@ -56,8 +58,9 @@
             return RemoteViews.startPendingIntent(hostView, pendingIntent,
                     remoteResponse.getLaunchOptions(view));
         }
-        if (mLauncher.getSplitToWorkspaceController().handleSecondWidgetSelectionForSplit(view,
-                pendingIntent)) {
+        if (mLauncher.isSplitSelectionEnabled()) {
+            Toast.makeText(hostView.getContext(), R.string.split_widgets_not_supported,
+                    Toast.LENGTH_SHORT).show();
             return true;
         }
         Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(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 28d4bf8..48d313e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -175,7 +175,7 @@
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
                 if (flag instanceof DeviceFlag) {
-                    pw.println("    " + flag);
+                    pw.println((flag.currentValueModified() ? "  ->" : "    ") + flag);
                 }
             }
         }
@@ -185,11 +185,11 @@
                 pw.println("    " + flag);
             }
         }
-        pw.println("DebugFlags:");
+        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/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/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/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 00d8008..92aa9fa 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -1222,7 +1222,7 @@
         }
         try {
             mBackAnimation.setBackToLauncherCallback(callback, runner);
-        } catch (RemoteException e) {
+        } catch (RemoteException | SecurityException e) {
             Log.e(TAG, "Failed call setBackToLauncherCallback", e);
         }
     }
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 3d8191d..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);
         }
 
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 b4fe5e4..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 {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 1b3d759..f264364 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -208,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);
@@ -218,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, TAG);
-                        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 07c85bb..d38376c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -120,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/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/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 3748a24..bdbdfd8 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS;
+import static com.android.launcher3.config.FeatureFlags.enableSplitFromFullscreenWithKeyboardShortcuts;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
@@ -191,7 +191,7 @@
     }
 
     private boolean shouldIgnoreSecondSplitLaunch() {
-        return (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
+        return (!enableSplitFromFullscreenWithKeyboardShortcuts()
                 && !FeatureFlags.enableSplitContextually()
                 && !isDesktopModeSupported())
                 || !mController.isSplitSelectActive();
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index f3fa86a..8f719d0 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS;
+import static com.android.launcher3.config.FeatureFlags.enableSplitFromFullscreenWithKeyboardShortcuts;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -75,7 +75,7 @@
 
     @BinderThread
     public void enterStageSplit(boolean leftOrTop) {
-        if (!ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()) {
+        if (!enableSplitFromFullscreenWithKeyboardShortcuts()) {
             return;
         }
         RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
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/ActionButton.kt b/quickstep/src/com/android/quickstep/views/ActionButton.kt
new file mode 100644
index 0000000..5c004cc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ActionButton.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.Button
+
+/**
+ * A button on the Overview Actions Bar. Custom logic for hiding/showing each button type is handled
+ * in the respective subclass.
+ */
+open class ActionButton : Button {
+    private var mHiddenFlags = 0
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    /**
+     * Updates the proper flags to indicate whether the button should be hidden.
+     *
+     * @param flag The flag to update.
+     * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
+     */
+    protected fun updateHiddenFlags(flag: Int, enable: Boolean) {
+        if (enable) {
+            mHiddenFlags = mHiddenFlags or flag
+        } else {
+            mHiddenFlags = mHiddenFlags and flag.inv()
+        }
+        val shouldBeVisible = mHiddenFlags == 0
+        this.visibility = if (shouldBeVisible) VISIBLE else GONE
+    }
+
+    /** Show/hide the button when the focused task is a single/pair. */
+    open fun updateForMultipleTasks(hasMultipleTasks: Boolean) {
+        // overridden in subclass, or else don't do anything
+    }
+
+    /** Show/hide the button depending on if the device is a tablet. */
+    open fun updateForTablet(isTablet: Boolean) {
+        // overridden in subclass, or else don't do anything
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 7f1d619..8692e4e 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -22,7 +22,6 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.widget.Button;
 import android.widget.FrameLayout;
 
 import androidx.annotation.IntDef;
@@ -41,6 +40,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * View for showing action buttons in Overview
@@ -89,14 +90,11 @@
     private static final int INDEX_SCROLL_ALPHA = 5;
     private static final int NUM_ALPHAS = 6;
 
-    public @interface SplitButtonHiddenFlags { }
-    public static final int FLAG_IS_NOT_TABLET = 1 << 0;
-
-    public @interface SplitButtonDisabledFlags { }
-    public static final int FLAG_SINGLE_TASK = 1 << 0;
-
     private MultiValueAlpha mMultiValueAlpha;
-    private Button mSplitButton;
+
+    private List<ActionButton> mActionButtons = new ArrayList<>();
+    private ScreenshotActionButton mScreenshotButton;
+    private SplitActionButton mSplitButton;
 
     @ActionsHiddenFlags
     private int mHiddenFlags;
@@ -104,12 +102,6 @@
     @ActionsDisabledFlags
     protected int mDisabledFlags;
 
-    @SplitButtonHiddenFlags
-    private int mSplitButtonHiddenFlags;
-
-    @SplitButtonDisabledFlags
-    private int mSplitButtonDisabledFlags;
-
     @Nullable
     protected T mCallbacks;
 
@@ -135,9 +127,12 @@
         mMultiValueAlpha = new MultiValueAlpha(findViewById(R.id.action_buttons), NUM_ALPHAS);
         mMultiValueAlpha.setUpdateVisibility(true);
 
-        findViewById(R.id.action_screenshot).setOnClickListener(this);
+        mScreenshotButton = findViewById(R.id.action_screenshot);
+        mScreenshotButton.setOnClickListener(this);
+        mActionButtons.add(mScreenshotButton);
         mSplitButton = findViewById(R.id.action_split);
         mSplitButton.setOnClickListener(this);
+        mActionButtons.add(mSplitButton);
     }
 
     /**
@@ -201,40 +196,28 @@
         }
         boolean isEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
         LayoutUtils.setViewEnabled(this, isEnabled);
-        updateSplitButtonEnabledState();
     }
 
     /**
-     * Updates the proper flags to indicate whether the "Split screen" button should be hidden.
-     *
-     * @param flag   The flag to update.
-     * @param enable Whether to enable the hidden flag: True will cause view to be hidden.
+     * Updates flags to hide and show actions buttons when a grouped task (split screen) is focused.
+     * @param isGroupedTask True if the focused task is a grouped task.
      */
-    public void updateSplitButtonHiddenFlags(@SplitButtonHiddenFlags int flag, boolean enable) {
-        if (enable) {
-            mSplitButtonHiddenFlags |= flag;
-        } else {
-            mSplitButtonHiddenFlags &= ~flag;
+    public void updateForGroupedTask(boolean isGroupedTask) {
+        for (ActionButton button : mActionButtons) {
+            // Update flags to show/hide buttons.
+            button.updateForMultipleTasks(isGroupedTask);
         }
-        if (mSplitButton == null) return;
-        boolean shouldBeVisible = mSplitButtonHiddenFlags == 0;
-        mSplitButton.setVisibility(shouldBeVisible ? VISIBLE : GONE);
-        findViewById(R.id.action_split_space).setVisibility(shouldBeVisible ? VISIBLE : GONE);
     }
 
     /**
-     * Updates the proper flags to indicate whether the "Split screen" button should be disabled.
-     *
-     * @param flag   The flag to update.
-     * @param enable Whether to enable the disable flag: True will cause view to be disabled.
+     * Updates flags to hide and show actions buttons depending on if the device is a tablet.
+     * @param isTablet True if the current device is a tablet.
      */
-    public void updateSplitButtonDisabledFlags(@SplitButtonDisabledFlags int flag, boolean enable) {
-        if (enable) {
-            mSplitButtonDisabledFlags |= flag;
-        } else {
-            mSplitButtonDisabledFlags &= ~flag;
+    public void updateForTablet(boolean isTablet) {
+        for (ActionButton button : mActionButtons) {
+            // Update flags to show/hide buttons.
+            button.updateForTablet(isTablet);
         }
-        updateSplitButtonEnabledState();
     }
 
     public MultiProperty getContentAlpha() {
@@ -312,18 +295,4 @@
                 (dp.isLandscape ? R.drawable.ic_split_horizontal : R.drawable.ic_split_vertical),
                 0, 0, 0);
     }
-
-    /**
-     * Enables/disables the "Split" button based on the status of mSplitButtonDisabledFlags and
-     * mDisabledFlags.
-     */
-    private void updateSplitButtonEnabledState() {
-        if (mSplitButton == null) {
-            return;
-        }
-        boolean isParentEnabled = (mDisabledFlags & ~DISABLED_ROTATED) == 0;
-        boolean shouldBeEnabled = mSplitButtonDisabledFlags == 0 && isParentEnabled;
-        mSplitButton.setEnabled(shouldBeEnabled);
-    }
-
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7972999..6a275e6 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -20,7 +20,6 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
-
 import static com.android.app.animation.Interpolators.ACCELERATE;
 import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
@@ -34,6 +33,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
@@ -42,7 +42,6 @@
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
@@ -57,8 +56,6 @@
 import static com.android.quickstep.util.LogUtils.splitFailureMessage;
 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
-import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
-import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -3927,18 +3924,23 @@
     }
 
     /**
-     * Hides all overview actions if current page is for split apps, shows otherwise
-     * If actions are showing, we only show split option if
+     * Hides all overview actions if user is halfway through split selection, shows otherwise.
+     * We only show split option if:
+     * * Focused view is a single app
      * * Device is large screen
-     * * There are at least 2 tasks to invoke split
      */
     private void updateCurrentTaskActionsVisibility() {
-        boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
-        mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
+        boolean isGroupedTask = getCurrentPageTaskView() != null
+                && getCurrentPageTaskView().containsMultipleTasks();
+        // Update flags to see if entire actions bar should be hidden.
+        mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isGroupedTask);
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
-        mActionsView.updateSplitButtonHiddenFlags(FLAG_IS_NOT_TABLET,
-                !mActivity.getDeviceProfile().isTablet);
-        mActionsView.updateSplitButtonDisabledFlags(FLAG_SINGLE_TASK, /*enable=*/ false);
+        // Update flags to see if actions bar should show buttons for a single task or a pair of
+        // tasks.
+        mActionsView.updateForGroupedTask(isGroupedTask);
+        // Update flags to see if split button should be hidden.
+        mActionsView.updateForTablet(mActivity.getDeviceProfile().isTablet);
+
         if (isDesktopModeSupported()) {
             boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
             mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
diff --git a/quickstep/src/com/android/quickstep/views/ScreenshotActionButton.kt b/quickstep/src/com/android/quickstep/views/ScreenshotActionButton.kt
new file mode 100644
index 0000000..9cb48b8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ScreenshotActionButton.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.content.Context
+import android.util.AttributeSet
+
+/** A button on the Overview Actions Bar for screenshotting the focused app. */
+class ScreenshotActionButton : ActionButton {
+    companion object {
+        const val FLAG_MULTIPLE_TASKS_HIDE_SCREENSHOT = 1 shl 0
+    }
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    /** Show/hide the button when the focused task is a single/pair. */
+    override fun updateForMultipleTasks(hasMultipleTasks: Boolean) {
+        // Hidden for multiple tasks
+        updateHiddenFlags(FLAG_MULTIPLE_TASKS_HIDE_SCREENSHOT, hasMultipleTasks)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/SplitActionButton.kt b/quickstep/src/com/android/quickstep/views/SplitActionButton.kt
new file mode 100644
index 0000000..88d1a77
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/SplitActionButton.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.content.Context
+import android.util.AttributeSet
+
+/** A button on the Overview Actions Bar for initiating split screen. */
+class SplitActionButton : ActionButton {
+    companion object {
+        const val FLAG_IS_NOT_TABLET_HIDE_SPLIT = 1 shl 0
+        const val FLAG_MULTIPLE_TASKS_HIDE_SPLIT = 1 shl 1
+    }
+
+    constructor(context: Context) : super(context)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(
+        context: Context,
+        attrs: AttributeSet?,
+        defStyleAttr: Int
+    ) : super(context, attrs, defStyleAttr)
+
+    /** Show/hide the button when the focused task is a single/pair. */
+    override fun updateForMultipleTasks(hasMultipleTasks: Boolean) {
+        // Hidden for multiple tasks
+        updateHiddenFlags(FLAG_MULTIPLE_TASKS_HIDE_SPLIT, hasMultipleTasks)
+    }
+
+    /** Show/hide the button depending on if the device is a tablet. */
+    override fun updateForTablet(isTablet: Boolean) {
+        // Hidden for non-tablets
+        updateHiddenFlags(FLAG_IS_NOT_TABLET_HIDE_SPLIT, !isTablet)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 94183c4..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();
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..9c7f014 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
@@ -12,6 +12,7 @@
 import androidx.test.runner.AndroidJUnit4
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
+import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION
 import com.android.launcher3.taskbar.TaskbarManager
 import com.android.systemui.shared.rotation.RotationButton
 import java.lang.IllegalStateException
@@ -59,7 +60,7 @@
 
     @Test
     fun getKidsLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = true
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
             getLayoutter(
@@ -74,7 +75,7 @@
 
     @Test
     fun getSetupLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = true
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
             getLayoutter(
@@ -89,7 +90,7 @@
 
     @Test
     fun getTaskbarNavLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = true
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
             getLayoutter(
@@ -104,7 +105,7 @@
 
     @Test(expected = IllegalStateException::class)
     fun noValidLayoutForLargeScreenTaskbarNotPresent() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         getLayoutter(
             isKidsMode = false,
@@ -117,7 +118,7 @@
 
     @Test
     fun getTaskbarPortraitLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
             getLayoutter(
@@ -132,7 +133,7 @@
 
     @Test
     fun getTaskbarLandscapeLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         setDeviceProfileLandscape()
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
@@ -148,7 +149,7 @@
 
     @Test
     fun getTaskbarSeascapeLayoutter() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(ENABLE_TASKBAR_NAVBAR_UNIFICATION)
         mockDeviceProfile.isTaskbarPresent = false
         setDeviceProfileLandscape()
         val layoutter: NavButtonLayoutFactory.NavButtonLayoutter =
@@ -164,7 +165,7 @@
 
     @Test(expected = IllegalStateException::class)
     fun noValidLayoutForPhoneGestureNav() {
-        assumeTrue(TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        assumeTrue(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 12568ea..17fa253 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -162,8 +162,7 @@
     @Before
     public void setUp() {
         mLauncher.onTestStart();
-        AbstractLauncherUiTest.waitForSetupWizardDismissal();
-        AbstractLauncherUiTest.verifyKeyguardInvisible();
+        AbstractLauncherUiTest.onTestStart();
     }
 
     @After
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index a5d7724..df88726 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -185,7 +185,7 @@
                         + launcher.getNavigationModeMismatchError(false),
                 () -> launcher.getNavigationModeMismatchError(false) == null,
                 WAIT_TIME_MS, launcher);
-        AbstractLauncherUiTest.checkDetectedLeaks(launcher, true);
+        AbstractLauncherUiTest.checkDetectedLeaks(launcher, false);
         return true;
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
index f51f286..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();
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 0ee0b67..ff8537b 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;
@@ -348,7 +351,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());
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index ee0fbb8..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());
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index dc57332..dd966cc 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Deursoek jou tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Misluk: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privaat ruimte"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 4bfea99..bc2cc86 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ጡባዊዎን ይፈልጉ"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"አልተሳካም፦ <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"የግል ቦታ"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 9cbfbfa..a3e5889 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"البحث في جهازك اللوحي"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"تعذَّر <xliff:g id="WHAT">%1$s</xliff:g>."</string>
     <string name="private_space_label" msgid="2359721649407947001">"مساحة خاصة"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 6368312..f7c1b40 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"আপোনাৰ টেবলেটৰ বস্তু সন্ধান কৰক"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"বিফল: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"ব্যক্তিগত স্পে’চ"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 2ec448d..3b20be8 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Planşetinizi axtarın"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Alınmadı: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Şəxsi yer"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 81da8a8..4b7b663 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Pretražite tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Nije uspelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privatni prostor"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 5bbac0f..4616b8e 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Пошук на планшэце"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Не ўдалося: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Прыватная вобласць"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index bdae209..c4be1f4 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Търсене в таблета ви"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Неуспешно: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Лично пространство"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 3a57581..a4dce63 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"আপনার ট্যাবলেটে সার্চ করুন"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"কাজটি করা যায়নি: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"ব্যক্তিগত স্পেস"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index e453982..9d7419f 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Pretražite tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Nije uspjelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privatan prostor"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index b03b4ec..fedea5d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Cerca a la tauleta"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Error: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Espai privat"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index a39a1d5..c721f56 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Vyhledávání na tabletu"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Selhalo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Soukromý prostor"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index faebb41..5c0423a 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Søg på din tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Mislykket: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privat rum"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 61e4859..40f1c74 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Auf dem Tablet suchen"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Fehler: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privater Bereich"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index b98ae4f..cc5d0bd 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Αναζήτηση στο tablet σας"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Αποτυχία: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Ιδιωτικός χώρος"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 8e55910..9b0cde6 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Search your tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index c5e5caf..c78355a 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Search your tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 8e55910..9b0cde6 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Search your tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 8e55910..9b0cde6 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Search your tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Failed: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Private space"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 1ad2059..9aaf4d2 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‎‎‎‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎Search your tablet‎‏‎‎‏‎"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‎‎Failed: ‎‏‎‎‏‏‎<xliff:g id="WHAT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="private_space_label" msgid="2359721649407947001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎Private space‎‏‎‎‏‎"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index b8d213b..3d830c4 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Busca en tu tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Error: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Espacio privado"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index c13ce1a..cb35617 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Buscar en tu tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Se ha producido un error: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Espacio privado"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 76bd494..60a0a04 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Otsimine tahvelarvutist"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Nurjus: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privaatne ruum"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index d73c885..4f4645f 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Bilatu tabletan"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Huts egin du: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Eremu pribatua"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index fc42f54..3129c10 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"جستجوی رایانه لوحی"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"ناموفق بود: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"فضای خصوصی"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index a567eba..9147b36 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Hae tabletilta"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Epäonnistui: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Yksityinen tila"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index a6f5053..b739c95 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Rechercher sur votre tablette"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Espace privé"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 9e09a5c..eca5f51 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Rechercher sur votre tablette"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Espace privé"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 41c30c2..82edb6d 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Facer buscas na tableta"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Erro: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Espazo privado"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 71aebc4..ca79a3f 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"તમારું ટૅબ્લેટ શોધો"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"નિષ્ફળ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"ખાનગી સ્પેસ"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index a9f9ecd..d1ea837 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"अपने टैबलेट में खोजें"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"पूरा नहीं हुआ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"प्राइवेट स्पेस"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 757f335..90f36b9 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Pretraživanje tableta"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Nije uspjelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privatni prostor"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 6c43388..bae2c81 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Keresés a táblagépen"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Sikertelen: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privát terület"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 6eae2b6..76857db 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Որոնում պլանշետում"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Չհաջողվեց կատարել գործողությունը (<xliff:g id="WHAT">%1$s</xliff:g>)"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Անձնական տարածք"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index a47ec3b..8301a5d 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Telusuri di tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Gagal: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Ruang pribadi"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 9e9be03..5bd6356 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Leita í spjaldtölvunni"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Mistókst: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Einkarými"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 407a492..dd385c3 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Ricerche sul tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Operazione non riuscita: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Spazio privato"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index e87cfaf..1eb4f4e 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"חיפוש בטאבלט"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"הפעולה נכשלה: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"מרחב פרטי"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index e8a72eb..35d97a9 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"タブレットを探す"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"失敗: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"プライベート スペース"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index ab6e962..b4af601 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ძიება თქვენს ტაბლეტში"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"ვერ მოხერხდა: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"პირადი სივრცე"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index c18a43f..9c15ca4 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Планшеттен іздеу"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Қате шықты: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Жеке бөлме"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 4aa1878..363690c 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ស្វែងរកក្នុង​ថេប្លេតរបស់អ្នក"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"បានបរាជ័យ៖ <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"បន្ទប់​ឯកជន"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 138e1b3..88aeab5 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ ಅನ್ನು ಹುಡುಕಿ"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"ವಿಫಲವಾಗಿದೆ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"ಖಾಸಗಿ ಸ್ಪೇಸ್"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 7803d09..1f92b4d 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"태블릿 속 항목 검색"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"실패: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"비공개 스페이스"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index a9da5b8..aa24cdd 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Планшетте издөө"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Аткарылган жок: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Жеке чөйрө"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 1dc9a91..dfa4d48 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ຊອກຫາແທັບເລັດຂອງທ່ານ"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"ບໍ່ສຳເລັດ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"ພື້ນທີ່ສ່ວນຕົວ"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 1666f47..d8e4b0c 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Paieška planšetiniame kompiuteryje"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Nepavyko: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privati erdvė"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 420d3d6..f2b63c2 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Meklēšana planšetdatorā"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Neizdevās: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privātā telpa"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 841aba7..5b7973a 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Пребарување низ таблетот"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Не успеа: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Приватен простор"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index df39e74..5507eb7 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"നിങ്ങളുടെ ടാബ്‌ലെറ്റിലുള്ളവ തിരയുക"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"പരാജയപ്പെട്ടു: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"സ്വകാര്യ സ്പേസ്"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index ad89209..2cc2ae3 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Таблетнаасаа хайх"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Амжилтгүй болсон: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Хувийн орон зай"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index a14459c..c0beed2 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"तुमच्या टॅबलेटमध्ये शोधा"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"हे करता आले नाही: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"खाजगी स्पेस"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 75a50b0..8ec5ba0 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Cari pada tablet anda"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Gagal: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Ruang peribadi"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index a4d2417..1463620 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"သင့်တက်ဘလက်ကို ရှာခြင်း"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"မအောင်မြင်ပါ− <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"သီးသန့်ချတ်ခန်း"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index aea53fa..90ff503 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Søk på nettbrettet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Mislyktes: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privat område"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index b5760d7..e02bf66 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"आफ्नो ट्याबलेटमा खोज्नुहोस्"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"कार्य पूरा गर्न सकिएन: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"निजी स्पेस"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 7f362ec..f143c1e 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Zoeken op je tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Mislukt: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privéruimte"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 406b176..4bf50fa 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ଆପଣଙ୍କ ଟାବଲେଟରେ ସନ୍ଧାନ କରନ୍ତୁ"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"ବିଫଳ ହୋଇଛି: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"ପ୍ରାଇଭେଟ ସ୍ପେସ"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 837eba0..0d9848e 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ਆਪਣਾ ਟੈਬਲੈੱਟ ਖੋਜੋ"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"ਇਹ ਕਾਰਵਾਈ ਅਸਫਲ ਹੋਈ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"ਨਿੱਜੀ ਸਪੇਸ"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 24f09f9..faf5dfa 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Przeszukuj tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Niepowodzenie: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Obszar prywatny"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 0febbd1..1b3a8de 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Pesquise no tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Falhou: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Espaço privado"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index ad8e0c0..ec0c02c 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Pesquisar no tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Falha: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Espaço particular"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 46f5b40..648f7e9 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Caută pe tabletă"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Eșuare: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Spațiu privat"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 79e1781..5918868 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Поиск на планшете"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Не удалось выполнить действие (<xliff:g id="WHAT">%1$s</xliff:g>)."</string>
     <string name="private_space_label" msgid="2359721649407947001">"Личное пространство"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 72c0491..f7cd68b 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ඔබගේ ටැබ්ලටය සොයන්න"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"අසාර්ථකයි: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"පෞද්ගලික ඉඩ"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index bf01b32..b519127 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Vyhľadávanie v tablete"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Zlyhalo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Súkromný priestor"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 52d8584..78c8b55 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Iskanje po tabličnem računalniku"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Ni uspelo: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Zasebni prostor"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 2c072ee..95ec874 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Kërko në tabletin tënd"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Dështoi: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Hapësira private"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index ee66f12..d58af08 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Претражите таблет"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Није успело: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Приватни простор"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 57fb8be..9cf1001 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Sök på surfplattan"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Misslyckades: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Privat rum"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index b735318..1797dd3 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Tafuta kwenye kompyuta kibao yako"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Hitilafu: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Nafasi ya faragha"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 85a7a66..c81f62a 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"உங்கள் டேப்லெட்டில் தேடுதல்"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"தோல்வி: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"தனிப்பட்ட சேமிப்பிடம்"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index ee4d7ca..6cfdd0b 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"మీ టాబ్లెట్‌లో సెర్చ్ చేయండి"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"విఫలమైంది: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"ప్రైవేట్ స్పేస్"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 047e2b1..4bfccaa 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"ค้นหาในแท็บเล็ต"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"ไม่สำเร็จ: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"พื้นที่ส่วนตัว"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index e17abc8..05af221 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Maghanap sa iyong tablet"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Hindi nagawa: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Pribadong space"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 9994adf..ec24a9d 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Tabletinizde arama yapma"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Başarısız: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Gizli alan"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index a638609..c7b2f7f 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Пошук на планшеті"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Не вдалося <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Приватний простір"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 92b8b41..857cf5b 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"اپنے ٹیبلیٹ پر تلاش کریں"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"ناکام ہو گيا: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"نجی اسپیس"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 2e3bfed..972a246 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Planshetingizni qidiring"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Xato: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Shaxsiy xona"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 85f48a6..f720c8d 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Tìm kiếm trong máy tính bảng của bạn"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Không thực hiện được thao tác: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Không gian riêng tư"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 69f6bcf..8c5bac0 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"平板电脑内搜索"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"失败:<xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"私密空间"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 1af7766..02225a7 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"搜尋平板電腦內容"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"操作失敗:<xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"私人空間"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 47d6f14..4483de4 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"搜尋平板電腦內容"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"失敗:<xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"私人空間"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 4871d8f..580a3be 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -176,4 +176,6 @@
     <string name="search_pref_screen_title_tablet" msgid="5220319680451343959">"Sesha ithebulethi yakho"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Yehlulekile: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Isikhala esiyimfihlo"</string>
+    <!-- no translation found for bubble_bar_overflow_description (7410995531938041192) -->
+    <skip />
 </resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f046eca..3682830 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -261,30 +261,34 @@
 
     <!--  Responsive grids attributes  -->
     <declare-styleable name="ResponsiveSpec">
-        <attr name="specType" format="integer">
+        <attr name="dimensionType" format="integer">
             <enum name="height" value="0" />
             <enum name="width" value="1" />
         </attr>
         <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="dimensionType" />
         <attr name="maxAvailableSize" />
     </declare-styleable>
 
     <declare-styleable name="FolderSpec">
-        <attr name="specType" />
+        <attr name="dimensionType" />
         <attr name="maxAvailableSize" />
     </declare-styleable>
 
     <declare-styleable name="AllAppsSpec">
-        <attr name="specType" />
+        <attr name="dimensionType" />
         <attr name="maxAvailableSize" />
     </declare-styleable>
 
     <declare-styleable name="HotseatSpec">
-        <attr name="specType" />
+        <attr name="dimensionType" />
         <attr name="maxAvailableSize" />
     </declare-styleable>
 
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/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 73cd8c4..fed031b 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 mResponsiveWorkspaceWidthSpec;
+    private CalculatedResponsiveSpec mResponsiveWorkspaceHeightSpec;
+    private CalculatedResponsiveSpec mResponsiveAllAppsWidthSpec;
+    private CalculatedResponsiveSpec mResponsiveAllAppsHeightSpec;
+    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,47 @@
         // 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;
+            int numWorkspaceColumns = getPanelCount() * inv.numColumns;
+            int numAllAppsColumns = getPanelCount() * inv.numAllAppsColumns;
             // 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);
+            mResponsiveWorkspaceWidthSpec = workspaceSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.WIDTH, numWorkspaceColumns, availableResponsiveWidth);
+            mResponsiveWorkspaceHeightSpec = 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);
+            mResponsiveAllAppsWidthSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.WIDTH, numAllAppsColumns, availableWidthPx,
+                    mResponsiveWorkspaceWidthSpec);
+            mResponsiveAllAppsHeightSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.HEIGHT, inv.numRows,  heightPx - mInsets.top,
+                    mResponsiveWorkspaceHeightSpec);
+
+            ResponsiveSpecsProvider folderSpecs = ResponsiveSpecsProvider.create(
+                    new ResourceHelper(context,
+                            isTwoPanels ? inv.folderSpecsTwoPanelId : inv.folderSpecsId),
+                    ResponsiveSpecType.Folder);
+            mResponsiveFolderWidthSpec = folderSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.WIDTH, inv.numFolderColumns,
+                    mResponsiveWorkspaceWidthSpec.getAvailableSpace(),
+                    mResponsiveWorkspaceWidthSpec);
+            mResponsiveFolderHeightSpec = folderSpecs.getCalculatedSpec(responsiveAspectRatio,
+                    DimensionType.HEIGHT, inv.numFolderRows,
+                    mResponsiveWorkspaceHeightSpec.getAvailableSpace(),
+                    mResponsiveWorkspaceHeightSpec);
         }
 
         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
@@ -745,7 +759,7 @@
 
     private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
         if (mIsResponsiveGrid) {
-            return mResponsiveWidthSpec.getStartPaddingPx();
+            return mResponsiveWorkspaceWidthSpec.getStartPaddingPx();
         }
 
         if (isVerticalBarLayout()) {
@@ -761,8 +775,8 @@
             InvariantDeviceProfile inv,
             int extraSpace) {
         if (mIsResponsiveGrid) {
-            workspaceTopPadding = mResponsiveHeightSpec.getStartPaddingPx();
-            workspaceBottomPadding = mResponsiveHeightSpec.getEndPaddingPx();
+            workspaceTopPadding = mResponsiveWorkspaceHeightSpec.getStartPaddingPx();
+            workspaceBottomPadding = mResponsiveWorkspaceHeightSpec.getEndPaddingPx();
         } else if (mIsScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) {
             // Paddings were created assuming no scaling, so we first unscale the extra space.
             int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
@@ -858,8 +872,8 @@
         int verticalSpacePx = 0;
 
         if (mIsResponsiveGrid) {
-            horizontalSpacePx = mResponsiveWidthSpec.getGutterPx();
-            verticalSpacePx = mResponsiveHeightSpec.getGutterPx();
+            horizontalSpacePx = mResponsiveWorkspaceWidthSpec.getGutterPx();
+            verticalSpacePx = mResponsiveWorkspaceHeightSpec.getGutterPx();
         } else if (mIsScalableGrid) {
             horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
             verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
@@ -1004,12 +1018,16 @@
                 + cellLayoutPaddingPx.left + cellLayoutPaddingPx.right;
     }
 
-    private int getNormalizedIconDrawablePadding() {
+    private int getNormalizedIconDrawablePadding(int iconSizePx, int iconDrawablePadding) {
         // TODO(b/235886078): workaround needed because of this bug
         // Icons are 10% larger on XML than their visual size,
         // so remove that extra space to get labels closer to the correct padding
         int iconVisibleSizePx = (int) Math.round(ICON_VISIBLE_AREA_FACTOR * iconSizePx);
-        return Math.max(0, mIconDrawablePaddingOriginalPx - ((iconSizePx - iconVisibleSizePx) / 2));
+        return Math.max(0, iconDrawablePadding - ((iconSizePx - iconVisibleSizePx) / 2));
+    }
+
+    private int getNormalizedIconDrawablePadding() {
+        return getNormalizedIconDrawablePadding(iconSizePx, mIconDrawablePaddingOriginalPx);
     }
 
     private int getNormalizedFolderChildDrawablePaddingPx(int textHeight) {
@@ -1042,8 +1060,8 @@
         cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
 
         if (mIsResponsiveGrid) {
-            cellWidthPx = mResponsiveWidthSpec.getCellSizePx();
-            cellHeightPx = mResponsiveHeightSpec.getCellSizePx();
+            cellWidthPx = mResponsiveWorkspaceWidthSpec.getCellSizePx();
+            cellHeightPx = mResponsiveWorkspaceHeightSpec.getCellSizePx();
 
             if (cellWidthPx < iconSizePx) {
                 // get a smaller icon size
@@ -1147,7 +1165,7 @@
 
         // All apps
         if (mIsResponsiveGrid) {
-            updateAllAppsWithResponsiveMeasures();
+            updateAllAppsWithResponsiveMeasures(res);
         } else {
             updateAllAppsIconSize(scale, res);
         }
@@ -1246,20 +1264,61 @@
         }
     }
 
-    private void updateAllAppsWithResponsiveMeasures() {
-        allAppsIconSizePx = iconSizePx;
-        allAppsIconTextSizePx = iconTextSizePx;
-        allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
-
+    private void updateAllAppsWithResponsiveMeasures(Resources res) {
         allAppsBorderSpacePx = new Point(
-                mAllAppsResponsiveWidthSpec.getGutterPx(),
-                mAllAppsResponsiveHeightSpec.getGutterPx()
+                mResponsiveAllAppsWidthSpec.getGutterPx(),
+                mResponsiveAllAppsHeightSpec.getGutterPx()
         );
-        allAppsCellHeightPx = mAllAppsResponsiveHeightSpec.getCellSizePx()
-                + mAllAppsResponsiveHeightSpec.getGutterPx();
-        allAppsCellWidthPx = mAllAppsResponsiveWidthSpec.getCellSizePx();
-        allAppsPadding.left = mAllAppsResponsiveWidthSpec.getStartPaddingPx();
-        allAppsPadding.right = mAllAppsResponsiveWidthSpec.getEndPaddingPx();
+        allAppsCellHeightPx = mResponsiveAllAppsHeightSpec.getCellSizePx()
+                + mResponsiveAllAppsHeightSpec.getGutterPx();
+        allAppsCellWidthPx = mResponsiveAllAppsWidthSpec.getCellSizePx();
+
+        // 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 = mResponsiveAllAppsWidthSpec.getStartPaddingPx() - halfBorder;
+        allAppsPadding.right = mResponsiveAllAppsWidthSpec.getEndPaddingPx() - halfBorder;
+
+        // TODO(b/287975993): Remove this after icon size is extracted to responsive grid
+        // Copy icon size from the workspace when spec is matchWorkspace or
+        // use the default all apps icon size
+        if (mResponsiveAllAppsHeightSpec.isCellSizeMatchWorkspace()
+                || mResponsiveAllAppsWidthSpec.isCellSizeMatchWorkspace()) {
+            allAppsIconSizePx = iconSizePx;
+            allAppsIconTextSizePx = iconTextSizePx;
+            allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
+        } else {
+            allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
+            allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
+            allAppsIconDrawablePaddingPx = res.getDimensionPixelSize(
+                    R.dimen.all_apps_icon_drawable_padding);
+            allAppsIconDrawablePaddingPx = getNormalizedIconDrawablePadding(allAppsIconSizePx,
+                    allAppsIconDrawablePaddingPx);
+        }
+
+        // Reduce the size of the app icon if it doesn't fit
+        if (allAppsCellWidthPx < allAppsIconSizePx) {
+            // get a smaller icon size
+            allAppsIconSizePx = mIconSizeSteps.getIconSmallerThan(allAppsCellWidthPx);
+        }
+
+        CellContentDimensions cellContentDimensions = new CellContentDimensions(
+                allAppsIconSizePx, allAppsIconDrawablePaddingPx, (int) allAppsIconTextSizePx);
+
+        if (allAppsCellHeightPx < cellContentDimensions.getCellContentHeight()) {
+            if (isVerticalBarLayout()) {
+                if (allAppsCellHeightPx < iconSizePx) {
+                    cellContentDimensions.setIconSizePx(
+                            mIconSizeSteps.getIconSmallerThan(allAppsCellHeightPx));
+                }
+            } else {
+                cellContentDimensions.resizeToFitCellHeight(allAppsCellHeightPx,
+                        mIconSizeSteps);
+            }
+            allAppsIconSizePx = cellContentDimensions.getIconSizePx();
+            allAppsIconDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
+            allAppsIconTextSizePx = cellContentDimensions.getIconTextSizePx();
+        }
     }
 
     /**
@@ -1280,7 +1339,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 +1397,6 @@
 
         if (mIsResponsiveGrid) {
             folderCellWidthPx = mResponsiveFolderWidthSpec.getCellSizePx();
-
-            // Height
             folderCellHeightPx = mResponsiveFolderHeightSpec.getCellSizePx();
             folderContentPaddingTop = mResponsiveFolderHeightSpec.getStartPaddingPx();
             folderFooterHeightPx = mResponsiveFolderHeightSpec.getEndPaddingPx();
@@ -1356,24 +1413,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);
@@ -1540,15 +1588,17 @@
         Rect padding = workspacePadding;
         if (isVerticalBarLayout()) {
             if (mIsResponsiveGrid) {
-                padding.top = mResponsiveHeightSpec.getStartPaddingPx();
+                padding.top = mResponsiveWorkspaceHeightSpec.getStartPaddingPx();
                 padding.bottom = Math.max(0,
-                        mResponsiveHeightSpec.getEndPaddingPx() - mInsets.bottom);
+                        mResponsiveWorkspaceHeightSpec.getEndPaddingPx() - mInsets.bottom);
                 if (isSeascape()) {
-                    padding.left = hotseatBarSizePx + mResponsiveWidthSpec.getEndPaddingPx();
-                    padding.right = mResponsiveWidthSpec.getStartPaddingPx();
+                    padding.left =
+                            hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx();
+                    padding.right = mResponsiveWorkspaceWidthSpec.getStartPaddingPx();
                 } else {
-                    padding.left = mResponsiveWidthSpec.getStartPaddingPx();
-                    padding.right = hotseatBarSizePx + mResponsiveWidthSpec.getEndPaddingPx();
+                    padding.left = mResponsiveWorkspaceWidthSpec.getStartPaddingPx();
+                    padding.right =
+                            hotseatBarSizePx + mResponsiveWorkspaceWidthSpec.getEndPaddingPx();
                 }
             } else {
                 padding.top = 0;
@@ -2070,12 +2120,14 @@
         writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight()));
         writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth()));
         if (mIsResponsiveGrid) {
-            writer.println(prefix + "\tmResponsiveHeightSpec:" + mResponsiveHeightSpec.toString());
-            writer.println(prefix + "\tmResponsiveWidthSpec:" + mResponsiveWidthSpec.toString());
-            writer.println(prefix + "\tmAllAppsResponsiveHeightSpec:"
-                    + mAllAppsResponsiveHeightSpec.toString());
-            writer.println(prefix + "\tmAllAppsResponsiveWidthSpec:"
-                    + mAllAppsResponsiveWidthSpec.toString());
+            writer.println(prefix + "\tmResponsiveWorkspaceHeightSpec:"
+                    + mResponsiveWorkspaceHeightSpec.toString());
+            writer.println(prefix + "\tmResponsiveWorkspaceWidthSpec:"
+                    + mResponsiveWorkspaceWidthSpec.toString());
+            writer.println(prefix + "\tmResponsiveAllAppsHeightSpec:"
+                    + mResponsiveAllAppsHeightSpec.toString());
+            writer.println(prefix + "\tmResponsiveAllAppsWidthSpec:"
+                    + mResponsiveAllAppsWidthSpec.toString());
             writer.println(prefix + "\tmResponsiveFolderHeightSpec:" + mResponsiveFolderHeightSpec);
             writer.println(prefix + "\tmResponsiveFolderWidthSpec:" + mResponsiveFolderWidthSpec);
             writer.println(prefix + "\tmResponsiveHotseatSpec:" + mResponsiveHotseatSpec);
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/Launcher.java b/src/com/android/launcher3/Launcher.java
index ec2816b..5a0cf9d 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 =
@@ -1605,6 +1590,9 @@
         if (getStateManager().isInStableState(ALL_APPS)) {
             getStateManager().goToState(NORMAL, alreadyOnHome);
         } else {
+            if (mWorkspace.isOverlayShown()) {
+                mOverlayManager.hideOverlay(/* animate */true);
+            }
             AbstractFloatingView.closeAllOpenViews(this);
             getStateManager().goToState(ALL_APPS, true /* animated */,
                     new AnimationSuccessListener() {
@@ -2803,10 +2791,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/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 72c6cb8..095cfa9 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));
@@ -1043,6 +1046,11 @@
         return getActiveAppsRecyclerView();
     }
 
+    /** The current focus change listener in the search container. */
+    public OnFocusChangeListener getSearchFocusChangeListener() {
+        return mAH.get(AdapterHolder.SEARCH).mOnFocusChangeListener;
+    }
+
     /** The current apps recycler view in the container. */
     private AllAppsRecyclerView getActiveAppsRecyclerView() {
         if (!mUsingTabs || isPersonalTab()) {
@@ -1098,7 +1106,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 +1116,6 @@
             }
             setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0);
         }
-
         InsettableFrameLayout.dispatchInsets(this, insets);
     }
 
@@ -1440,6 +1447,7 @@
         final AlphabeticalAppsList<T> mAppsList;
         final Rect mPadding = new Rect();
         AllAppsRecyclerView mRecyclerView;
+        private OnFocusChangeListener mOnFocusChangeListener;
 
         AdapterHolder(int type, AlphabeticalAppsList<T> appsList) {
             mType = type;
@@ -1463,7 +1471,8 @@
             onInitializeRecyclerView(mRecyclerView);
             FocusedItemDecorator focusedItemDecorator = new FocusedItemDecorator(mRecyclerView);
             mRecyclerView.addItemDecoration(focusedItemDecorator);
-            mAdapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
+            mOnFocusChangeListener = focusedItemDecorator.getFocusListener();
+            mAdapter.setIconFocusListener(mOnFocusChangeListener);
             applyPadding();
         }
 
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/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 73861c1..51eb363 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getDebugFlag;
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getIntFlag;
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getReleaseFlag;
+import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
 
 import android.view.ViewConfiguration;
 
@@ -217,12 +218,19 @@
     public static final BooleanFlag ENABLE_TRANSIENT_TASKBAR = getDebugFlag(270395798,
             "ENABLE_TRANSIENT_TASKBAR", ENABLED, "Enables transient taskbar.");
 
+    public static final boolean ENABLE_TASKBAR_NAVBAR_UNIFICATION =
+            enableTaskbarNavbarUnification();
+
     // Aconfig migration complete for ENABLE_TASKBAR_NO_RECREATION.
     public static final BooleanFlag ENABLE_TASKBAR_NO_RECREATION = getDebugFlag(299193589,
             "ENABLE_TASKBAR_NO_RECREATION", DISABLED,
             "Enables taskbar with no recreation from lifecycle changes of TaskbarActivityContext.");
     public static boolean enableTaskbarNoRecreate() {
-        return ENABLE_TASKBAR_NO_RECREATION.get() || Flags.enableTaskbarNoRecreate();
+        return ENABLE_TASKBAR_NO_RECREATION.get() || Flags.enableTaskbarNoRecreate()
+                // Task bar pinning and task bar nav bar unification are both dependent on
+                // ENABLE_TASKBAR_NO_RECREATION. We want to turn ENABLE_TASKBAR_NO_RECREATION on
+                // when either of the dependent features is turned on.
+                || ENABLE_TASKBAR_PINNING.get() || ENABLE_TASKBAR_NAVBAR_UNIFICATION;
     }
 
     // TODO(Block 16): Clean up flags
@@ -279,11 +287,16 @@
                     "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,
+    // Aconfig migration complete for ENABLE_TASKBAR_PINNING.
+    private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
             "ENABLE_TASKBAR_PINNING", TEAMFOOD,
             "Enables taskbar pinning to allow user to switch between transient and persistent "
                     + "taskbar flavors");
 
+    public static boolean enableTaskbarPinning() {
+        return ENABLE_TASKBAR_PINNING.get() || Flags.enableTaskbarPinning();
+    }
+
     public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag(
             251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED,
             "Marks LauncherPref data as (and allows it to) available while the device is"
@@ -395,11 +408,16 @@
             "USE_LOCAL_ICON_OVERRIDES", ENABLED,
             "Use inbuilt monochrome icons if app doesn't provide one");
 
-    // TODO(Block 28): Clean up flags
+    // Aconfig migration complete for ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.
     public static final BooleanFlag ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS =
             getDebugFlag(270394122, "ENABLE_SPLIT_FROM_FULLSCREEN_SHORTCUT", DISABLED,
                     "Enable splitting from fullscreen app with keyboard shortcuts");
+    public static boolean enableSplitFromFullscreenWithKeyboardShortcuts() {
+        return ENABLE_SPLIT_FROM_FULLSCREEN_WITH_KEYBOARD_SHORTCUTS.get()
+                || Flags.enableSplitFromFullscreenWithKeyboardShortcuts();
+    }
 
+    // Aconfig migration complete for ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.
     public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE = getDebugFlag(
             270393453, "ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE", DISABLED,
             "Enable initiating split screen from workspace to workspace.");
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 2e5f2e5..b39e968e 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
@@ -434,7 +435,8 @@
             drawable.setLevel(item.getProgressLevel());
             p.drawable = drawable;
         } else {
-            p.drawable = item.newIcon(mContext, FLAG_THEMED);
+            p.drawable = item.newIcon(mContext,
+                    Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0);
         }
         p.drawable.setBounds(0, 0, mIconSize, mIconSize);
         p.item = item;
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/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 685e4f1..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;
                 }
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 69%
rename from src/com/android/launcher3/responsive/HotseatSpecs.kt
rename to src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
index 37a682f..8710303 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
+                        R.styleable.ResponsiveSpec_dimensionType,
+                        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 60%
rename from src/com/android/launcher3/responsive/ResponsiveSpecs.kt
rename to src/com/android/launcher3/responsive/ResponsiveSpec.kt
index a43c44a..413e2dc 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_dimensionType,
+                        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,10 @@
  * 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 aspectRatio: Float = Float.NaN
+        private set
+
     var availableSpace: Int = 0
         private set
 
@@ -143,11 +162,13 @@
         private set
 
     constructor(
+        aspectRatio: Float,
         availableSpace: Int,
         cells: Int,
         spec: ResponsiveSpec,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+        calculatedWorkspaceSpec: CalculatedResponsiveSpec
     ) {
+        this.aspectRatio = aspectRatio
         this.availableSpace = availableSpace
         this.cells = cells
         this.spec = spec
@@ -167,7 +188,8 @@
         updateRemainderSpaces(availableSpace, cells, spec)
     }
 
-    constructor(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
+    constructor(aspectRatio: Float, availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
+        this.aspectRatio = aspectRatio
         this.availableSpace = availableSpace
         this.cells = cells
         this.spec = spec
@@ -181,6 +203,11 @@
         updateRemainderSpaces(availableSpace, cells, spec)
     }
 
+    fun isResponsiveSpecType(type: ResponsiveSpecType) = spec.specType == type
+
+    // TODO(b/287975993): Remove this after icon size is extracted to responsive grid
+    fun isCellSizeMatchWorkspace(): Boolean = spec.cellSize.matchWorkspace
+
     private fun updateRemainderSpaces(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
         val gutters = cells - 1
         val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
@@ -215,10 +242,11 @@
     }
 
     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}" +
+            "aspectRatio=${aspectRatio}, " +
+            "${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..67eaac0
--- /dev/null
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecsProvider.kt
@@ -0,0 +1,137 @@
+/*
+ * 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(aspectRatio, 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(
+            aspectRatio,
+            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/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/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 26ab5b4..0470971 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -22,8 +22,8 @@
 import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
 import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
 import static com.android.launcher3.Utilities.dpiFromPx;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_PINNING;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TRANSIENT_TASKBAR;
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
 import static com.android.launcher3.util.window.WindowManagerProxy.MIN_TABLET_WIDTH;
@@ -116,7 +116,7 @@
         mContext = context;
         mDM = context.getSystemService(DisplayManager.class);
 
-        if (ENABLE_TASKBAR_PINNING.get()) {
+        if (enableTaskbarPinning()) {
             attachTaskbarPinningSharedPreferenceChangeListener(mContext);
         }
 
@@ -179,7 +179,7 @@
     @Override
     public void close() {
         mDestroyed = true;
-        if (ENABLE_TASKBAR_PINNING.get()) {
+        if (enableTaskbarPinning()) {
             LauncherPrefs.get(mContext).removeListener(
                     mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
         }
@@ -414,7 +414,7 @@
             //  sTransientTaskbarStatusForTests and update test to directly
             //  toggle shred preference to switch transient taskbar on/of
             if (!Utilities.isRunningInTestHarness()
-                    && ENABLE_TASKBAR_PINNING.get()
+                    && enableTaskbarPinning()
                     && mIsTaskbarPinned) {
                 return false;
             }
diff --git a/src/com/android/launcher3/util/IconSizeSteps.kt b/src/com/android/launcher3/util/IconSizeSteps.kt
index aa644b0..6128eb4 100644
--- a/src/com/android/launcher3/util/IconSizeSteps.kt
+++ b/src/com/android/launcher3/util/IconSizeSteps.kt
@@ -39,8 +39,8 @@
         return steps[max(0, getIndexForIconSize(iconSizePx) - 1)]
     }
 
-    fun getIconSmallerThan(cellWidth: Int): Int {
-        return steps.lastOrNull { it <= cellWidth } ?: steps[0]
+    fun getIconSmallerThan(cellSize: Int): Int {
+        return steps.lastOrNull { it <= cellSize } ?: steps[0]
     }
 
     private fun getIndexForIconSize(iconSizePx: Int): Int {
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..2a7a346 100644
--- a/tests/res/values/attrs.xml
+++ b/tests/res/values/attrs.xml
@@ -19,25 +19,29 @@
 <resources>
     <!--  Responsive grids attributes  -->
     <declare-styleable name="ResponsiveSpec">
-        <attr name="specType" format="integer">
+        <attr name="dimensionType" format="integer">
             <enum name="height" value="0" />
             <enum name="width" value="1" />
         </attr>
         <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="dimensionType" />
         <attr name="maxAvailableSize" />
     </declare-styleable>
 
     <declare-styleable name="FolderSpec">
-        <attr name="specType" />
+        <attr name="dimensionType" />
         <attr name="maxAvailableSize" />
     </declare-styleable>
 
     <declare-styleable name="AllAppsSpec">
-        <attr name="specType" />
+        <attr name="dimensionType" />
         <attr name="maxAvailableSize" />
     </declare-styleable>
 
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..d89e675 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:dimensionType="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:dimensionType="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..d5540f2 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:dimensionType="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:dimensionType="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..75c2172 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:dimensionType="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:dimensionType="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..6583d62 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:dimensionType="width" launcher:maxAvailableSize="800dp">
+            <!--  missing startPadding  -->
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </folderSpec>
+        <folderSpec launcher:dimensionType="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:dimensionType="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..95ac856 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:dimensionType="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:dimensionType="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:dimensionType="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..f71040d 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:dimensionType="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:dimensionType="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:dimensionType="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..24b8ef6 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:dimensionType="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..6d6f577 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:dimensionType="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:dimensionType="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..8e0ce2c 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:dimensionType="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:dimensionType="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..d1bcf65
--- /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:dimensionType="height">
+        <cellSize launcher:fixedSize="104dp" />
+        <endPadding launcher:ofRemainderSpace="1" />
+        <gutter launcher:fixedSize="16dp" />
+        <startPadding launcher:fixedSize="8dp" />
+    </workspaceSpec>
+
+    <workspaceSpec
+        launcher:maxAvailableSize="9999dp"
+        launcher:dimensionType="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..49e1f93
--- /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:dimensionType="height">
+            <cellSize launcher:fixedSize="104dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="8dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:dimensionType="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:dimensionType="width">
+        <cellSize launcher:ofRemainderSpace="0.25" />
+        <endPadding launcher:fixedSize="22dp" />
+        <gutter launcher:fixedSize="16dp" />
+        <startPadding launcher:fixedSize="22dp" />
+    </workspaceSpec>
+    <workspaceSpec
+        launcher:maxAvailableSize="9999dp"
+        launcher:dimensionType="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..37fee06
--- /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:dimensionType="height">
+            <cellSize launcher:fixedSize="104dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="8dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:dimensionType="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:dimensionType="height">
+            <cellSize launcher:fixedSize="104dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <startPadding launcher:fixedSize="8dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:dimensionType="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..7a234d1 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:dimensionType="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:dimensionType="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:dimensionType="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..6638285 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:dimensionType="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:dimensionType="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:dimensionType="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..cb3e63f 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:dimensionType="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:dimensionType="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:dimensionType="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..901278d 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:dimensionType="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:dimensionType="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:dimensionType="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..33ea9b6 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:dimensionType="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:dimensionType="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..ecff9b8 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:dimensionType="width" launcher:maxAvailableSize="800dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:matchWorkspace="true" />
+        </folderSpec>
+        <folderSpec launcher:dimensionType="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:dimensionType="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..65c1d8b 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:dimensionType="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:dimensionType="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..f67adad 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:dimensionType="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:dimensionType="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..9a463d5
--- /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:dimensionType="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:dimensionType="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:dimensionType="width"
+            launcher:maxAvailableSize="716dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="36dp" />
+            <endPadding launcher:fixedSize="80dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="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:dimensionType="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:dimensionType="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:dimensionType="height"
+            launcher:maxAvailableSize="612dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:ofRemainderSpace="1" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="104dp" />
+        </workspaceSpec>
+
+        <workspaceSpec
+            launcher:dimensionType="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:dimensionType="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:dimensionType="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..9c44502 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:dimensionType="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:dimensionType="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:dimensionType="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:dimensionType="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:dimensionType="height"
+            launcher:maxAvailableSize="371dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="24dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="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:dimensionType="width"
+            launcher:maxAvailableSize="602dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="36dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="716dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="64dp" />
+            <gutter launcher:fixedSize="12dp" />
+            <cellSize launcher:ofRemainderSpace="0.25" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="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..6bf7c78 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:dimensionType="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:dimensionType="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:dimensionType="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:dimensionType="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
index 80f73cb..fe9464a 100644
--- a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.assertTrue;
 
 import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowInsets;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -76,4 +78,39 @@
             allApps.unfreeze();
         }
     }
+
+    @Test
+    public void testAllAppsExitSearchAndFocusSearchResults() {
+        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_C, 0);
+            waitForLauncherCondition("Search view not active.",
+                    launcher -> launcher.getAppsView().getActiveRecyclerView()
+                            instanceof SearchRecyclerView);
+            mLauncher.unpressKeyCode(KeyEvent.KEYCODE_C, 0);
+
+            executeOnLauncher(launcher -> launcher.getAppsView().getSearchUiManager().getEditText()
+                    .hideKeyboard(/* clearFocus= */ false));
+            waitForLauncherCondition("Keyboard still visible.", launcher -> {
+                View root = launcher.getDragLayer();
+                WindowInsets insets = root.getRootWindowInsets();
+                return insets != null && !insets.isVisible(WindowInsets.Type.ime());
+            });
+
+            mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
+            mLauncher.unpressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
+            waitForLauncherCondition("No focused child", launcher ->
+                    launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()
+                            != null);
+        } finally {
+            allApps.unfreeze();
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
index 7027e85..a690543 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemove.java
@@ -100,8 +100,7 @@
      */
     @Test
     @PortraitLandscape
-    // TODO(b/293944634): Remove Screenrecord after flaky debug, and add
-    // @PlatinumTest(focusArea = "launcher") back
+    @PlatinumTest(focusArea = "launcher")
     @ScreenRecordRule.ScreenRecord
     public void testUninstallFromWorkspace() throws Exception {
         installDummyAppAndWaitForUIUpdate();
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..5cfa49f
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
@@ -0,0 +1,306 @@
+/*
+ * 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(aspectRatio, 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(aspectRatio, 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(aspectRatio, 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(aspectRatio, 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(aspectRatio, 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(aspectRatio, 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 9bfafcf..688f418 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;
@@ -104,12 +105,20 @@
     private static boolean sDumpWasGenerated = false;
     private static boolean sActivityLeakReported = false;
     private static boolean sSeenKeyguard = false;
+    private static boolean sFirstTimeWaitingForWizard = true;
 
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
-    protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
-    protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation();
+    protected final UiDevice mDevice = getUiDevice();
+    protected final LauncherInstrumentation mLauncher = createLauncherInstrumentation();
+
+    @NonNull
+    private static LauncherInstrumentation createLauncherInstrumentation() {
+        waitForSetupWizardDismissal(); // precondition for creating LauncherInstrumentation
+        return new LauncherInstrumentation();
+    }
+
     protected Context mTargetContext;
     protected String mTargetPackage;
     private int mLauncherPid;
@@ -121,7 +130,10 @@
 
     /** Detects activity leaks and throws an exception if a leak is found. */
     public static void checkDetectedLeaks(LauncherInstrumentation launcher,
-            boolean requireOneActiveActivity) {
+            boolean requireOneActiveActivityUnused) {
+        final boolean requireOneActiveActivity =
+                false; // workaround for leaks when there is an unexpected Recents activity
+
         if (sActivityLeakReported) return;
 
         // Check whether activity leak detector has found leaked activities.
@@ -160,7 +172,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);
                 }
@@ -248,13 +260,6 @@
     public void setUp() throws Exception {
         mLauncher.onTestStart();
 
-        waitForSetupWizardDismissal();
-        if (TestStabilityRule.isPresubmit()) {
-            aggressivelyUnlockSysUi();
-        } else {
-            verifyKeyguardInvisible();
-        }
-
         final String launcherPackageName = mDevice.getLauncherPackageName();
         try {
             final Context context = InstrumentationRegistry.getContext();
@@ -285,23 +290,40 @@
             }
         }
 
-        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() {
+        waitForSetupWizardDismissal();
+
+        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(
@@ -309,21 +331,13 @@
         Log.d(TAG, "Keyguard is not visible");
     }
 
-    // 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 (sFirstTimeWaitingForWizard && TestStabilityRule.isPresubmit()) {
+    private static void waitForSetupWizardDismissal() {
+        if (!TestStabilityRule.isPresubmit()) return;
+
+        if (sFirstTimeWaitingForWizard) {
             try {
-                UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+                getUiDevice().executeShellCommand(
                         "am force-stop com.google.android.setupwizard");
             } catch (IOException e) {
                 throw new RuntimeException(e);
@@ -334,12 +348,10 @@
                 Until.gone(By.pkg("com.google.android.setupwizard").depth(0)),
                 sFirstTimeWaitingForWizard ? 120000 : 0);
         sFirstTimeWaitingForWizard = false;
-        // b/309496273
-//        Assert.assertTrue("Setup wizard is still visible",
-//                wizardDismissed);
+        Assert.assertTrue("Setup wizard is still visible", wizardDismissed);
     }
 
-    public static void verifyKeyguardInvisible() {
+    private static void verifyKeyguardInvisible() {
         final boolean keyguardAlreadyVisible = sSeenKeyguard;
 
         sSeenKeyguard = sSeenKeyguard
@@ -453,7 +465,6 @@
     // 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/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index 7db3161..32793cc 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -23,6 +23,8 @@
 import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -53,6 +55,7 @@
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.WidgetManagerHelper;
 
@@ -139,6 +142,7 @@
     }
 
     @Test
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/310242894
     public void testPendingWidget_withConfigScreen() {
         // A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
         // Do not bind the widget
@@ -188,6 +192,7 @@
     }
 
     @Test
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/310242894
     public void testPendingWidget_notRestored_brokenInstall() {
         // A widget which is was being installed once, even if its not being
         // installed at the moment is not removed.
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/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 17169b3..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() + ")";
     }
 
@@ -2063,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);