Merge "Clean up NPEs in AbsSwipeUpHandler" 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/taskbar_divider_popup_menu.xml b/quickstep/res/layout/taskbar_divider_popup_menu.xml
index 4348a47..7f4f76c 100644
--- a/quickstep/res/layout/taskbar_divider_popup_menu.xml
+++ b/quickstep/res/layout/taskbar_divider_popup_menu.xml
@@ -38,6 +38,7 @@
         android:theme="@style/PopupItem">
 
         <View
+            android:id="@+id/taskbar_pinning_visibility_icon"
             android:layout_margin="6dp"
             android:layout_width="20dp"
             android:layout_height="20dp"
@@ -45,14 +46,18 @@
             android:backgroundTint="?android:attr/textColorPrimary" />
 
         <Switch
-            style="@style/BaseIcon"
+            style="@style/Switch.SettingsLib"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:id="@+id/taskbar_pinning_switch"
             android:background="@null"
             android:clickable="false"
             android:gravity="start|center_vertical"
             android:textAlignment="viewStart"
             android:paddingStart="12dp"
-            android:singleLine="true"
+            android:layout_weight="1"
+            android:fontFamily="@*android:string/config_bodyFontFamilyMedium"
+            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/dimens.xml b/quickstep/res/values/dimens.xml
index aaa699b..22f98fa 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -386,6 +386,7 @@
 
     <!--- Taskbar Pinning -->
     <dimen name="taskbar_pinning_popup_menu_width">300dp</dimen>
+    <dimen name="taskbar_pinning_popup_menu_vertical_margin">16dp</dimen>
 
     <!-- Recents overview -->
     <dimen name="recents_filter_icon_size">30dp</dimen>
diff --git a/quickstep/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/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index d13b53f..3f9b66a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -15,22 +15,29 @@
  */
 package com.android.launcher3.taskbar
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
 import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.Rect
 import android.graphics.drawable.GradientDrawable
 import android.util.AttributeSet
+import android.util.Property
 import android.view.Gravity
 import android.view.MotionEvent
 import android.view.View
 import android.widget.LinearLayout
 import android.widget.Switch
 import androidx.core.view.postDelayed
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.launcher3.R
 import com.android.launcher3.popup.ArrowPopup
 import com.android.launcher3.popup.RoundedArrowDrawable
 import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
 
 /** Popup view with arrow for taskbar pinning */
 class TaskbarDividerPopupView<T : TaskbarActivityContext>
@@ -42,7 +49,8 @@
 ) : ArrowPopup<T>(context, attrs, defStyleAttr) {
     companion object {
         private const val TAG = "TaskbarDividerPopupView"
-        private const val DIVIDER_POPUP_CLOSING_DELAY = 500L
+        private const val DIVIDER_POPUP_CLOSING_DELAY = 333L
+        private const val DIVIDER_POPUP_CLOSING_ANIMATION_DURATION = 83L
 
         @JvmStatic
         fun createAndPopulate(
@@ -63,7 +71,7 @@
     private lateinit var dividerView: View
 
     private val menuWidth =
-        context.resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
+        resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_width)
     private val popupCornerRadius = Themes.getDialogCornerRadius(context)
     private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
     private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
@@ -71,6 +79,8 @@
 
     private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context)
     private var didPreferenceChange = false
+    private var verticalOffsetForPopupView =
+        resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_vertical_margin)
 
     /** Callback invoked when the pinning popup view is closing. */
     var onCloseCallback: (preferenceChanged: Boolean) -> Unit = {}
@@ -94,11 +104,22 @@
         super.onFinishInflate()
         val taskbarSwitchOption = requireViewById<LinearLayout>(R.id.taskbar_switch_option)
         val alwaysShowTaskbarSwitch = requireViewById<Switch>(R.id.taskbar_pinning_switch)
+        val taskbarVisibilityIcon = requireViewById<View>(R.id.taskbar_pinning_visibility_icon)
         alwaysShowTaskbarSwitch.isChecked = alwaysShowTaskbarOn
-        taskbarSwitchOption.setOnClickListener {
-            alwaysShowTaskbarSwitch.isClickable = true
-            alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
-            onClickAlwaysShowTaskbarSwitchOption()
+        if (ActivityContext.lookupContext<TaskbarActivityContext>(context).isGestureNav) {
+            taskbarSwitchOption.setOnClickListener {
+                alwaysShowTaskbarSwitch.isClickable = true
+                alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
+                onClickAlwaysShowTaskbarSwitchOption()
+            }
+        } else {
+            alwaysShowTaskbarSwitch.isEnabled = false
+        }
+
+        if (!alwaysShowTaskbarSwitch.isEnabled) {
+            taskbarVisibilityIcon.background.setTint(
+                resources.getColor(android.R.color.system_neutral2_500, context.theme)
+            )
         }
     }
 
@@ -171,10 +192,71 @@
         }
     }
 
-    override fun closeComplete() {
+    override fun getExtraVerticalOffset(): Int {
+        return (mActivityContext.deviceProfile.taskbarHeight -
+            mActivityContext.deviceProfile.taskbarIconSize) / 2 + verticalOffsetForPopupView
+    }
+
+    override fun animateClose() {
+        if (!mIsOpen) {
+            return
+        }
+        if (mOpenCloseAnimator != null) {
+            mOpenCloseAnimator.cancel()
+        }
+        mIsOpen = false
+
+        mOpenCloseAnimator = getCloseAnimator()
+
+        mOpenCloseAnimator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    mOpenCloseAnimator = null
+                    if (mDeferContainerRemoval) {
+                        setVisibility(INVISIBLE)
+                    } else {
+                        closeComplete()
+                    }
+                }
+            }
+        )
         onCloseCallback(didPreferenceChange)
         onCloseCallback = {}
-        super.closeComplete()
+        mOpenCloseAnimator.start()
+    }
+
+    private fun getCloseAnimator(): AnimatorSet {
+        val alphaValues = floatArrayOf(1f, 0f)
+        val translateYValue =
+            if (!alwaysShowTaskbarOn) verticalOffsetForPopupView else -verticalOffsetForPopupView
+        val alpha = getAnimatorOfFloat(this, ALPHA, *alphaValues)
+        val arrowAlpha = getAnimatorOfFloat(mArrow, ALPHA, *alphaValues)
+        val translateY =
+            ObjectAnimator.ofFloat(
+                this,
+                TRANSLATION_Y,
+                *floatArrayOf(this.translationY, this.translationY + translateYValue)
+            )
+        val arrowTranslateY =
+            ObjectAnimator.ofFloat(
+                mArrow,
+                TRANSLATION_Y,
+                *floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue)
+            )
+        val animatorSet = AnimatorSet()
+        animatorSet.playTogether(alpha, arrowAlpha, translateY, arrowTranslateY)
+        return animatorSet
+    }
+
+    private fun getAnimatorOfFloat(
+        view: View,
+        property: Property<View, Float>,
+        vararg values: Float
+    ): Animator {
+        val animator: Animator = ObjectAnimator.ofFloat(view, property, *values)
+        animator.setDuration(DIVIDER_POPUP_CLOSING_ANIMATION_DURATION)
+        animator.interpolator = EMPHASIZED_ACCELERATE
+        return animator
     }
 
     private fun onClickAlwaysShowTaskbarSwitchOption() {
@@ -182,7 +264,7 @@
         // Allow switch animation to finish and then close the popup.
         postDelayed(DIVIDER_POPUP_CLOSING_DELAY) {
             if (isOpen) {
-                close(false)
+                close(true)
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 1eb8d53..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 881f5c4..1a34b7a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -41,7 +41,8 @@
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
 import com.android.launcher3.anim.AlphaUpdateListener
-import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NO_RECREATION
+import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION
+import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.util.DisplayController
 import java.io.PrintWriter
@@ -99,7 +100,7 @@
             }
 
         windowLayoutParams.providedInsets =
-            if (ENABLE_TASKBAR_NO_RECREATION.get()) {
+            if (enableTaskbarNoRecreate()) {
                 getProvidedInsets(controllers.sharedState!!.insetsFrameProviders!!,
                         insetsRoundedCornerFlag)
             } else {
@@ -225,7 +226,6 @@
             provider.insetsSize = Insets.of(0, 0, rightIndexInset, 0)
         }
 
-
         // When in gesture nav, report the stashed height to the IME, to allow hiding the
         // IME navigation bar.
         val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
@@ -236,6 +236,12 @@
         val imeInsetsSizeOverride =
                 arrayOf(
                         InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+                        InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
+                                // No-op override to keep the size and types in sync with the
+                                // override below (insetsSizeOverrides must have the same length and
+                                // types after the window is added according to
+                                // WindowManagerService#relayoutWindow)
+                                provider.insetsSize)
                 )
         // Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
         val visInsetsSizeForTappableElement =
@@ -244,12 +250,11 @@
         val insetsSizeOverrideForTappableElement =
                 arrayOf(
                         InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
-                        InsetsFrameProvider.InsetsSizeOverride(
-                                TYPE_VOICE_INTERACTION,
+                        InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
                                 visInsetsSizeForTappableElement
                         ),
                 )
-        if ((context.isGestureNav || TaskbarManager.FLAG_HIDE_NAVBAR_WINDOW)
+        if ((context.isGestureNav || 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/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index d1bed3e..cbfa024 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -82,9 +82,12 @@
             taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
         )
 
+        controllers.taskbarOverlayController.hideWindow()
+
         animatorSet.doOnEnd { recreateTaskbarAndUpdatePinningValue() }
         animatorSet.duration = PINNING_ANIMATION_DURATION
         updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(true)
+        taskbarViewController.animateAwayNotificationDotsDuringTaskbarPinningAnimation()
         animatorSet.start()
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/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 0d06088..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;
@@ -52,6 +53,7 @@
 import androidx.core.view.OneShotPreDrawListener;
 
 import com.android.app.animation.Interpolators;
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
@@ -213,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);
         }
     }
@@ -232,7 +234,7 @@
     }
 
     public void onDestroy() {
-        if (ENABLE_TASKBAR_PINNING.get()) {
+        if (enableTaskbarPinning()) {
             mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
         }
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
@@ -338,6 +340,17 @@
         }
     }
 
+    /**
+     * Animate away taskbar icon notification dots during the taskbar pinning animation.
+     */
+    public void animateAwayNotificationDotsDuringTaskbarPinningAnimation() {
+        for (View iconView : mTaskbarView.getIconViews()) {
+            if (iconView instanceof BubbleTextView && ((BubbleTextView) iconView).hasDot()) {
+                ((BubbleTextView) iconView).animateDotScale(0);
+            }
+        }
+    }
+
     private void updateTaskbarIconTranslationXForPinning() {
         View[] iconViews = mTaskbarView.getIconViews();
         float scale = mTaskbarIconTranslationXForPinning.value;
@@ -354,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];
@@ -581,6 +598,11 @@
      *                       1 => fully aligned
      */
     public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
+        if (isPhoneMode(launcherDp)) {
+            mIconAlignControllerLazy = null;
+            return;
+        }
+
         boolean isHotseatIconOnTopWhenAligned =
                 mControllers.uiController.isHotseatIconOnTopWhenAligned();
         boolean isStashed = mControllers.taskbarStashController.isStashed();
@@ -609,11 +631,6 @@
      */
     private AnimatorPlaybackController createIconAlignmentController(DeviceProfile launcherDp) {
         PendingAnimation setter = new PendingAnimation(100);
-        if (TaskbarManager.isPhoneMode(launcherDp)) {
-            // No animation for icons in small-screen
-            return setter.createPlaybackController();
-        }
-
         mOnControllerPreCreateCallback.run();
         DeviceProfile taskbarDp = mActivity.getDeviceProfile();
         Rect hotseatPadding = launcherDp.getHotseatLayoutPadding(mActivity);
@@ -659,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) {
@@ -714,13 +731,19 @@
                 continue;
             }
 
-            int positionInHotseat;
-            if (isAllAppsButton || isTaskbarDividerView) {
-                // Note that there is no All Apps button or taskbar divider view in the hotseat,
+            float positionInHotseat;
+            if (isAllAppsButton) {
+                // Note that there is no All Apps button in the hotseat,
                 // this position is only used as its convenient for animation purposes.
                 positionInHotseat = Utilities.isRtl(child.getResources())
                         ? taskbarDp.numShownHotseatIcons
                         : -1;
+            }  else if (isTaskbarDividerView) {
+                // Note that there is no taskbar divider view in the hotseat,
+                // this position is only used as its convenient for animation purposes.
+                positionInHotseat = Utilities.isRtl(child.getResources())
+                        ? taskbarDp.numShownHotseatIcons - 0.5f
+                        : -0.5f;
             } else if (child.getTag() instanceof ItemInfo) {
                 positionInHotseat = ((ItemInfo) child.getTag()).screenId;
             } else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index 12cb8c5..6549ad6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -163,6 +163,7 @@
         mBubble = overflow;
         mBubbleIcon.setImageBitmap(bitmap);
         mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
+        setContentDescription(getResources().getString(R.string.bubble_bar_overflow_description));
     }
 
     /** Returns the bubble being rendered in this view. */
diff --git a/quickstep/src/com/android/launcher3/uioverrides/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 6279f63..48d313e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -57,6 +57,7 @@
     public static final String NAMESPACE_LAUNCHER = "launcher";
 
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
+    private static final List<IntFlag> sIntFlags = new ArrayList<>();
     private static SharedPreferences sSharedPreferences;
 
     static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
@@ -132,7 +133,14 @@
     public static IntFlag getIntFlag(
             int bugId, String key, int defaultValueInCode, String description) {
         INSTANCE.mKeySet.add(key);
-        return new IntFlag(DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode));
+        int defaultValue = DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode);
+        if (IS_DEBUG_DEVICE) {
+            IntDeviceFlag flag = new IntDeviceFlag(key, defaultValue, defaultValueInCode);
+            sIntFlags.add(flag);
+            return flag;
+        } else {
+            return new IntFlag(defaultValue);
+        }
     }
 
     static List<DebugFlag> getDebugFlags() {
@@ -163,18 +171,25 @@
             return;
         }
         pw.println("DeviceFlags:");
+        pw.println("  BooleanFlags:");
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
                 if (flag instanceof DeviceFlag) {
-                    pw.println("  " + flag);
+                    pw.println((flag.currentValueModified() ? "  ->" : "    ") + flag);
                 }
             }
         }
-        pw.println("DebugFlags:");
+        pw.println("  IntFlags:");
+        synchronized (sIntFlags) {
+            for (IntFlag flag : sIntFlags) {
+                pw.println("    " + flag);
+            }
+        }
+        pw.println("  DebugFlags:");
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
                 if (!(flag instanceof DeviceFlag)) {
-                    pw.println("  " + flag);
+                    pw.println((flag.currentValueModified() ? "  ->" : "    ") + flag);
                 }
             }
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java
new file mode 100644
index 0000000..4f3b0ae
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides.flags;
+
+import com.android.launcher3.config.FeatureFlags.IntFlag;
+
+public class IntDeviceFlag extends IntFlag {
+    public final String key;
+    private final int mDefaultValueInCode;
+
+    public IntDeviceFlag(String key, int currentValue, int defaultValueInCode) {
+        super(currentValue);
+        this.key = key;
+        mDefaultValueInCode = defaultValueInCode;
+    }
+
+    @Override
+    public String toString() {
+        return key + ": mCurrentValue=" + get() + ", defaultValueInCode=" + mDefaultValueInCode;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 87c774f..8313e09 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -353,7 +353,7 @@
                         return ROTATION_0;
                     }
                     return mRecentsView.getPagedViewOrientedState().getRecentsActivityRotation();
-                }, inputConsumer, /* callback = */ () -> {
+                }, inputConsumer, /* onTouchDownCallback = */ () -> {
                     endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
                     endLauncherTransitionController();
                 }, new InputProxyHandlerFactory(mActivityInterface, mGestureState));
@@ -1946,7 +1946,7 @@
         mCurrentShift.cancelAnimation();
 
         // Cleanup when switching handlers
-        mInputConsumerProxy.unregisterCallback();
+        mInputConsumerProxy.unregisterOnTouchDownCallback();
         mActivityInitListener.unregister();
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
                 mActivityRestartListener);
@@ -1958,7 +1958,7 @@
             mInputConsumerProxy.destroy();
             mTaskAnimationManager.setLiveTileCleanUpHandler(null);
         }
-        mInputConsumerProxy.unregisterCallback();
+        mInputConsumerProxy.unregisterOnTouchDownCallback();
         endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index c5a88bc..57b9a39 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -66,6 +66,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
 import com.android.quickstep.util.TransformParams;
@@ -151,19 +152,20 @@
             return new FallbackPipToHomeAnimationFactory();
         }
         mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
-        startHomeIntent(mActiveAnimationFactory, runningTaskTarget);
+        startHomeIntent(mActiveAnimationFactory, runningTaskTarget, "FallbackSwipeHandler-home");
         return mActiveAnimationFactory;
     }
 
     private void startHomeIntent(
             @Nullable FallbackHomeAnimationFactory gestureContractAnimationFactory,
-            @Nullable RemoteAnimationTarget runningTaskTarget) {
+            @Nullable RemoteAnimationTarget runningTaskTarget,
+            @NonNull String reason) {
         ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
         Intent intent = new Intent(mGestureState.getHomeIntent());
         if (gestureContractAnimationFactory != null && runningTaskTarget != null) {
             gestureContractAnimationFactory.addGestureContract(intent, runningTaskTarget.taskInfo);
         }
-        startHomeIntentSafely(mContext, intent, options.toBundle());
+        startHomeIntentSafely(mContext, intent, options.toBundle(), reason);
     }
 
     @Override
@@ -185,8 +187,8 @@
             // the PiP task appearing.
             recentsCallback = () -> {
                 callback.run();
-                startHomeIntent(
-                        null /* gestureContractAnimationFactory */, null /* runningTaskTarget */);
+                startHomeIntent(null /* gestureContractAnimationFactory */,
+                        null /* runningTaskTarget */, "FallbackSwipeHandler-resumeLauncher");
             };
         } else {
             recentsCallback = callback;
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 49ec85e..575be5f 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -29,13 +29,15 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
 
-import android.annotation.Nullable;
 import android.annotation.TargetApi;
 import android.content.Intent;
 import android.os.Build;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
@@ -303,6 +305,7 @@
     /**
      * @return the running task for this gesture.
      */
+    @Nullable
     public CachedTaskInfo getRunningTask() {
         return mRunningTask;
     }
@@ -336,7 +339,7 @@
     /**
      * Updates the running task for the gesture to be the given {@param runningTask}.
      */
-    public void updateRunningTask(CachedTaskInfo runningTask) {
+    public void updateRunningTask(@NonNull CachedTaskInfo runningTask) {
         mRunningTask = runningTask;
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 5784c37..feaa063 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -276,8 +276,11 @@
     @Override
     public boolean isInLiveTileMode() {
         Launcher launcher = getCreatedActivity();
-        return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
-                launcher.isStarted();
+
+        return launcher != null
+                && launcher.getStateManager().getState() == OVERVIEW
+                && launcher.isStarted()
+                && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 833bf5f..31fe791 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.util.RunnableList;
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -220,6 +221,7 @@
                 return true;
             }
             if (cmd.type == TYPE_HOME) {
+                ActiveGestureLog.INSTANCE.addLog("OverviewCommandHelper.executeCommand(TYPE_HOME)");
                 mService.startActivity(mOverviewComponentObserver.getHomeIntent());
                 return true;
             }
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 60713cf..0a02e99 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -39,6 +39,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
 import java.io.PrintWriter;
@@ -276,20 +277,24 @@
     /**
      * Starts the intent for the current home activity.
      */
-    public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options) {
+    public static void startHomeIntentSafely(@NonNull Context context, @Nullable Bundle options,
+            @NonNull String reason) {
         RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(context);
         OverviewComponentObserver observer = new OverviewComponentObserver(context, deviceState);
         Intent intent = observer.getHomeIntent();
         observer.onDestroy();
         deviceState.destroy();
-        startHomeIntentSafely(context, intent, options);
+        startHomeIntentSafely(context, intent, options, reason);
     }
 
     /**
      * Starts the intent for the current home activity.
      */
     public static void startHomeIntentSafely(
-            @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options) {
+            @NonNull Context context, @NonNull Intent homeIntent, @Nullable Bundle options,
+            @NonNull String reason) {
+        ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                "OverviewComponentObserver.startHomeIntent: ").append(reason));
         try {
             context.startActivity(homeIntent, options);
         } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 961d8c0..39edd70 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -94,6 +94,7 @@
  * See {@link com.android.quickstep.views.RecentsView}.
  */
 public final class RecentsActivity extends StatefulActivity<RecentsState> {
+    private static final String TAG = "RecentsActivity";
 
     public static final ActivityTracker<RecentsActivity> ACTIVITY_TRACKER =
             new ActivityTracker<>();
@@ -427,7 +428,7 @@
                 new RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
                 new RemoteTransition(runner.toRemoteTransition(), getIApplicationThread(),
                         "StartHomeFromRecents"));
-        startHomeIntentSafely(this, options.toBundle());
+        startHomeIntentSafely(this, options.toBundle(), TAG);
     }
 
     private final RemoteAnimationFactory mAnimationToHomeFactory =
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 8d26fd4..db5ad82 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -23,9 +23,9 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 import static com.android.launcher3.util.NavigationMode.THREE_BUTTONS;
-import static com.android.launcher3.util.NavigationMode.TWO_BUTTONS;
 import static com.android.launcher3.util.SettingsCache.ONE_HANDED_ENABLED;
 import static com.android.launcher3.util.SettingsCache.ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -55,8 +55,11 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.util.Log;
+import android.view.ISystemGestureExclusionListener;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
+import android.view.WindowManagerGlobal;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
@@ -74,7 +77,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
-import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
@@ -86,6 +88,8 @@
  */
 public class RecentsAnimationDeviceState implements DisplayInfoChangeListener {
 
+    private static final String TAG = "RecentsAnimationDeviceState";
+
     static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
     // TODO: Move to quickstep contract
@@ -95,6 +99,23 @@
     private final Context mContext;
     private final DisplayController mDisplayController;
     private final int mDisplayId;
+
+    private final ISystemGestureExclusionListener mGestureExclusionListener =
+            new ISystemGestureExclusionListener.Stub() {
+                @BinderThread
+                @Override
+                public void onSystemGestureExclusionChanged(int displayId,
+                        Region systemGestureExclusionRegion, Region unrestrictedOrNull) {
+                    if (displayId != mDisplayId) {
+                        return;
+                    }
+                    // Assignments are atomic, it should be safe on binder thread. Also we don't
+                    // think systemGestureExclusionRegion can be null but just in case, don't let
+                    // mExclusionRegion be null.
+                    mExclusionRegion = systemGestureExclusionRegion != null
+                            ? systemGestureExclusionRegion : new Region();
+                }
+            };
     private final RotationTouchHelper mRotationTouchHelper;
     private final TaskStackChangeListener mPipListener;
     // Cache for better performance since it doesn't change at runtime.
@@ -118,7 +139,7 @@
 
     private int mGestureBlockingTaskId = -1;
     private @NonNull Region mExclusionRegion = new Region();
-    private SystemGestureExclusionListenerCompat mExclusionListener;
+    private boolean mExclusionListenerRegistered;
 
     public RecentsAnimationDeviceState(Context context) {
         this(context, false);
@@ -142,19 +163,7 @@
         }
 
         // Register for exclusion updates
-        mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
-            @Override
-            @BinderThread
-            public void onExclusionChanged(Region region) {
-                if (region == null) {
-                    // Don't think this is possible but just in case, don't let it be null.
-                    region = new Region();
-                }
-                // Assignments are atomic, it should be safe on binder thread
-                mExclusionRegion = region;
-            }
-        };
-        runOnDestroy(mExclusionListener::unregister);
+        runOnDestroy(() -> unregisterExclusionListener());
 
         // Register for display changes changes
         mDisplayController.addChangeListener(this);
@@ -247,13 +256,54 @@
             mNavBarPosition = new NavBarPosition(mMode, info);
 
             if (mMode == NO_BUTTON) {
-                mExclusionListener.register();
+                registerExclusionListener();
             } else {
-                mExclusionListener.unregister();
+                unregisterExclusionListener();
             }
         }
     }
 
+    /**
+     * Registers {@link mGestureExclusionListener} for getting exclusion rect changes. Note that we
+     * make binder call on {@link UI_HELPER_EXECUTOR} to avoid jank.
+     */
+    public void registerExclusionListener() {
+        UI_HELPER_EXECUTOR.execute(() -> {
+            if (mExclusionListenerRegistered) {
+                return;
+            }
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .registerSystemGestureExclusionListener(
+                                mGestureExclusionListener, mDisplayId);
+                mExclusionListenerRegistered = true;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to register window manager callbacks", e);
+            }
+        });
+    }
+
+    /**
+     * Unregisters {@link mGestureExclusionListener} if previously registered. We make binder call
+     * on same {@link UI_HELPER_EXECUTOR} as in {@link #registerExclusionListener()} so that
+     * read/write {@link mExclusionListenerRegistered} field is thread safe.
+     */
+    public void unregisterExclusionListener() {
+        UI_HELPER_EXECUTOR.execute(() -> {
+            if (!mExclusionListenerRegistered) {
+                return;
+            }
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .unregisterSystemGestureExclusionListener(
+                                mGestureExclusionListener, mDisplayId);
+                mExclusionListenerRegistered = false;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to unregister window manager callbacks", e);
+            }
+        });
+    }
+
     public void onOneHandedModeChanged(int newGesturalHeight) {
         mRotationTouchHelper.setGesturalHeight(newGesturalHeight);
     }
@@ -280,13 +330,6 @@
     }
 
     /**
-     * @return whether the current nav mode is 2-button-based.
-     */
-    public boolean isTwoButtonNavMode() {
-        return mMode == TWO_BUTTONS;
-    }
-
-    /**
      * @return whether the current nav mode is button-based.
      */
     public boolean isButtonNavMode() {
@@ -391,13 +434,6 @@
     }
 
     /**
-     * @return whether notification panel is expanded
-     */
-    public boolean isNotificationPanelExpanded() {
-        return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0;
-    }
-
-    /**
      * @return whether the global actions dialog is showing
      */
     public boolean isSystemUiDialogShowing() {
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 038a022..92aa9fa 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -17,6 +17,7 @@
 
 import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
 
+import static com.android.launcher3.testing.shared.TestProtocol.testLogD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -97,6 +98,7 @@
 import com.android.wm.shell.transition.IShellTransitions;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
@@ -398,6 +400,17 @@
     }
 
     @Override
+    public void animateNavBarLongPress(boolean isTouchDown, long durationMs) {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.animateNavBarLongPress(isTouchDown, durationMs);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call animateNavBarLongPress", e);
+            }
+        }
+    }
+
+    @Override
     public void notifyAccessibilityButtonClicked(int displayId) {
         if (mSystemUiProxy != null) {
             try {
@@ -1209,7 +1222,7 @@
         }
         try {
             mBackAnimation.setBackToLauncherCallback(callback, runner);
-        } catch (RemoteException e) {
+        } catch (RemoteException | SecurityException e) {
             Log.e(TAG, "Failed call setBackToLauncherCallback", e);
         }
     }
@@ -1249,19 +1262,21 @@
     }
 
     public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
-        if (mRecentTasks != null) {
-            try {
-                final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,
-                        RECENT_IGNORE_UNAVAILABLE, userId);
-                if (rawTasks == null) {
-                    return new ArrayList<>();
-                }
-                return new ArrayList<>(Arrays.asList(rawTasks));
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getRecentTasks", e);
-            }
+        if (mRecentTasks == null) {
+            Log.w(TAG, "getRecentTasks() failed due to null mRecentTasks");
+            return new ArrayList<>();
         }
-        return new ArrayList<>();
+        try {
+            final GroupedRecentTaskInfo[] rawTasks = mRecentTasks.getRecentTasks(numTasks,
+                    RECENT_IGNORE_UNAVAILABLE, userId);
+            if (rawTasks == null) {
+                return new ArrayList<>();
+            }
+            return new ArrayList<>(Arrays.asList(rawTasks));
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed call getRecentTasks", e);
+            return new ArrayList<>();
+        }
     }
 
     /**
@@ -1405,7 +1420,7 @@
     public boolean startRecentsActivity(Intent intent, ActivityOptions options,
             RecentsAnimationListener listener) {
         if (mRecentTasks == null) {
-            ActiveGestureLog.INSTANCE.trackEvent(RECENT_TASKS_MISSING);
+            ActiveGestureLog.INSTANCE.addLog("Null mRecentTasks", RECENT_TASKS_MISSING);
             return false;
         }
         final IRecentsAnimationRunner runner = new IRecentsAnimationRunner.Stub() {
@@ -1463,4 +1478,35 @@
             return false;
         }
     }
+
+    public void dump(PrintWriter pw) {
+        pw.println(TAG + ":");
+
+        pw.println("\tmSystemUiProxy=" + mSystemUiProxy);
+        pw.println("\tmPip=" + mPip);
+        pw.println("\tmPipAnimationListener=" + mPipAnimationListener);
+        pw.println("\tmBubbles=" + mBubbles);
+        pw.println("\tmBubblesListener=" + mBubblesListener);
+        pw.println("\tmSplitScreen=" + mSplitScreen);
+        pw.println("\tmSplitScreenListener=" + mSplitScreenListener);
+        pw.println("\tmSplitSelectListener=" + mSplitSelectListener);
+        pw.println("\tmOneHanded=" + mOneHanded);
+        pw.println("\tmShellTransitions=" + mShellTransitions);
+        pw.println("\tmHomeTransitionListener=" + mHomeTransitionListener);
+        pw.println("\tmStartingWindow=" + mStartingWindow);
+        pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
+        pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
+        pw.println("\tmLauncherActivityClass=" + mLauncherActivityClass);
+        pw.println("\tmLauncherUnlockAnimationController=" + mLauncherUnlockAnimationController);
+        pw.println("\tmRecentTasks=" + mRecentTasks);
+        pw.println("\tmRecentTasksListener=" + mRecentTasksListener);
+        pw.println("\tmBackAnimation=" + mBackAnimation);
+        pw.println("\tmBackToLauncherCallback=" + mBackToLauncherCallback);
+        pw.println("\tmBackToLauncherRunner=" + mBackToLauncherRunner);
+        pw.println("\tmDesktopMode=" + mDesktopMode);
+        pw.println("\tmDesktopTaskListener=" + mDesktopTaskListener);
+        pw.println("\tmUnfoldAnimation=" + mUnfoldAnimation);
+        pw.println("\tmUnfoldAnimationListener=" + mUnfoldAnimationListener);
+        pw.println("\tmDragAndDrop=" + mDragAndDrop);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index f0f8aec..20a751b 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.Flags.enableOverviewIconMenu;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 
 import android.annotation.Nullable;
@@ -120,6 +121,7 @@
             public void handleResult(TaskCacheEntry result) {
                 task.icon = result.icon;
                 task.titleDescription = result.contentDescription;
+                task.title = result.title;
                 callback.accept(task);
                 dispatchIconUpdate(task.key.id);
             }
@@ -190,6 +192,10 @@
         if (activityInfo != null) {
             entry.contentDescription = getBadgedContentDescription(
                     activityInfo, task.key.userId, task.taskDescription);
+            if (enableOverviewIconMenu()) {
+                entry.title = Utilities.trim(
+                        activityInfo.applicationInfo.loadLabel(mContext.getPackageManager()));
+            }
         }
 
         mIconCache.put(task.key, entry);
@@ -280,6 +286,7 @@
     private static class TaskCacheEntry {
         public Drawable icon;
         public String contentDescription = "";
+        public String title = "";
     }
 
     void registerTaskVisualsChangeListener(TaskVisualsChangeListener newListener) {
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 662a895..ddddc89 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -197,7 +197,11 @@
                 remoteTargetHandles = gluer.assignTargets(targets);
             }
         }
+        final int recentsActivityRotation =
+                recentsView.getPagedViewOrientedState().getRecentsActivityRotation();
         for (RemoteTargetHandle remoteTargetGluer : remoteTargetHandles) {
+            remoteTargetGluer.getTaskViewSimulator().getOrientationState().setRecentsRotation(
+                    recentsActivityRotation);
             remoteTargetGluer.getTransformParams().setSyncTransactionApplier(applier);
         }
 
@@ -693,15 +697,17 @@
             launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE);
             launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
 
-            windowAnimEndListener = new AnimatorListenerAdapter() {
+            windowAnimEndListener = new AnimationSuccessListener() {
                 @Override
                 public void onAnimationStart(Animator animation) {
                     recentsView.onTaskLaunchedInLiveTileMode();
                 }
 
                 // Make sure recents gets fixed up by resetting task alphas and scales, etc.
+                // This should only be run onAnimationSuccess, otherwise finishRecentsAnimation will
+                // interfere with a rapid swipe up to home in the live tile + running task case.
                 @Override
-                public void onAnimationEnd(Animator animation) {
+                public void onAnimationSuccess(Animator animation) {
                     recentsView.finishRecentsAnimation(false /* toRecents */, () -> {
                         recentsView.post(() -> {
                             stateManager.moveToRestState();
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index f1af2ed..a2a6dde 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -29,6 +29,7 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
@@ -178,6 +179,7 @@
     /**
      * Returns the CachedTaskInfo for the top most task
      */
+    @NonNull
     @UiThread
     public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
         if (filterOnlyVisibleRecents) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 98a9938..0ad56fb 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -854,7 +854,11 @@
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             gestureState = new GestureState(mOverviewComponentObserver,
                     ActiveGestureLog.INSTANCE.getLogId());
-            taskInfo = previousGestureState.getRunningTask();
+            TopTaskTracker.CachedTaskInfo previousTaskInfo = previousGestureState.getRunningTask();
+            // previousTaskInfo can be null iff previousGestureState == GestureState.DEFAULT_STATE
+            taskInfo = previousTaskInfo != null
+                    ? previousTaskInfo
+                    : TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
             gestureState.updateRunningTask(taskInfo);
             gestureState.updateLastStartedTaskIds(previousGestureState.getLastStartedTaskIds());
             gestureState.updatePreviouslyAppearedTaskIds(
@@ -869,7 +873,7 @@
 
         // Log initial state for the gesture.
         ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=")
-                .append(taskInfo == null ? "no running task" : taskInfo.getPackageName()));
+                .append(taskInfo.getPackageName()));
         ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current SystemUi state flags=")
                 .append(mDeviceState.getSystemUiStateString()));
         return gestureState;
@@ -1078,21 +1082,22 @@
 
         reasonString.append(SUBSTRING_PREFIX).append("keyguard is not showing occluded");
 
+        TopTaskTracker.CachedTaskInfo runningTask = gestureState.getRunningTask();
         // Use overview input consumer for sharesheets on top of home.
         boolean forceOverviewInputConsumer = gestureState.getActivityInterface().isStarted()
-                && gestureState.getRunningTask() != null
-                && gestureState.getRunningTask().isRootChooseActivity();
+                && runningTask != null
+                && runningTask.isRootChooseActivity();
 
         // In the case where we are in an excluded, translucent overlay, ignore it and treat the
         // running activity as the task behind the overlay.
-        TopTaskTracker.CachedTaskInfo otherVisibleTask = gestureState.getRunningTask() == null
+        TopTaskTracker.CachedTaskInfo otherVisibleTask = runningTask == null
                 ? null
-                : gestureState.getRunningTask().otherVisibleTaskThisIsExcludedOver();
+                : runningTask.otherVisibleTaskThisIsExcludedOver();
         if (otherVisibleTask != null) {
             ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
                     .append(otherVisibleTask.getPackageName())
                     .append(" because the previous task running on top of this one (")
-                    .append(gestureState.getRunningTask().getPackageName())
+                    .append(runningTask.getPackageName())
                     .append(") was excluded from recents"));
             gestureState.updateRunningTask(otherVisibleTask);
         }
@@ -1112,7 +1117,7 @@
                     forceOverviewInputConsumer,
                     reasonString.append(SUBSTRING_PREFIX)
                             .append("is in live tile mode, trying to use overview input consumer"));
-        } else if (gestureState.getRunningTask() == null) {
+        } else if (runningTask == null) {
             return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
                     .append("running task == null"));
         } else if (previousGestureAnimatedToLauncher
@@ -1130,7 +1135,7 @@
                                             ? "launcher resumed through a shell transition"
                                             : "forceOverviewInputConsumer == true"))
                             .append(", trying to use overview input consumer"));
-        } else if (mDeviceState.isGestureBlockedTask(gestureState.getRunningTask())) {
+        } else if (mDeviceState.isGestureBlockedTask(runningTask)) {
             return getDefaultInputConsumer(reasonString.append(SUBSTRING_PREFIX)
                     .append("is gesture-blocked task, trying to use default input consumer"));
         } else {
@@ -1299,6 +1304,7 @@
         Log.i(TAG, "preloadOverview: forSUWAllSet=" + forSUWAllSet
                 + ", isHomeAndOverviewSame=" + mOverviewComponentObserver.isHomeAndOverviewSame());
 
+        ActiveGestureLog.INSTANCE.addLog("preloadRecentsAnimation");
         mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
     }
 
@@ -1373,6 +1379,7 @@
         mTaskbarManager.dumpLogs("", pw);
         pw.println("AssistStateManager:");
         AssistStateManager.INSTANCE.get(this).dump("  ", pw);
+        SystemUiProxy.INSTANCE.get(this).dump(pw);
     }
 
     private AbsSwipeUpHandler createLauncherSwipeHandler(
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 2a35584..f264364 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -68,6 +68,7 @@
  */
 public class DeviceLockedInputConsumer implements InputConsumer,
         RecentsAnimationCallbacks.RecentsAnimationListener, BuilderProxy {
+    private final String TAG = "DeviceLockedInputConsumer";
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null;
     private static int getFlagForIndex(int index, String name) {
@@ -207,7 +208,12 @@
             animator.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    if (ENABLE_SHELL_TRANSITIONS) {
+                    if (dismissTask) {
+                        // Just start the home intent so the user is prompted to unlock the device.
+                        // This will come back and cancel the interaction.
+                        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
+                        mHomeLaunched = true;
+                    } else if (ENABLE_SHELL_TRANSITIONS) {
                         if (mTaskAnimationManager.getCurrentCallbacks() != null) {
                             if (mRecentsAnimationController != null) {
                                 finishRecentsAnimationForShell(dismissTask);
@@ -217,11 +223,6 @@
                                 mDismissTask = dismissTask;
                             }
                         }
-                    } else if (dismissTask) {
-                        // For now, just start the home intent so user is prompted to
-                        // unlock the device.
-                        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
-                        mHomeLaunched = true;
                     }
                     mStateCallback.setState(STATE_HANDLER_INVALIDATED);
                 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index 32f7bd5..d38376c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -38,7 +38,6 @@
     private final NavHandleLongPressHandler mNavHandleLongPressHandler;
     private final float mNavHandleWidth;
     private final float mScreenWidth;
-    private final ViewConfiguration mViewConfiguration;
 
     private final Runnable mTriggerLongPress = this::triggerLongPress;
     private final float mTouchSlopSquared;
@@ -49,7 +48,6 @@
     public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
             InputMonitorCompat inputMonitor, RecentsAnimationDeviceState deviceState) {
         super(delegate, inputMonitor);
-        mViewConfiguration = ViewConfiguration.get(context);
         mNavHandleWidth = context.getResources().getDimensionPixelSize(
                 R.dimen.navigation_home_handle_width);
         mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
@@ -69,6 +67,18 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
+        if (mDelegate.allowInterceptByParent()) {
+            handleMotionEvent(ev);
+        } else if (MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)) {
+            cancelLongPress();
+        }
+
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
+        }
+    }
+
+    private void handleMotionEvent(MotionEvent ev) {
         switch (ev.getAction()) {
             case MotionEvent.ACTION_DOWN -> {
                 if (mCurrentDownEvent != null) {
@@ -103,10 +113,6 @@
             MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
             MAIN_EXECUTOR.getHandler().post(mTriggerLongPress);
         }
-
-        if (mState != STATE_ACTIVE) {
-            mDelegate.onMotionEvent(ev);
-        }
     }
 
     private void triggerLongPress() {
@@ -114,7 +120,7 @@
         if (longPressRunnable != null) {
             OtherActivityInputConsumer oaic = getInputConsumerOfClass(
                     OtherActivityInputConsumer.class);
-            if (oaic != null) {
+            if (oaic != null && oaic.hasStartedTouchTracking()) {
                 oaic.setForceFinishRecentsTransitionCallback(longPressRunnable);
                 setActive(mCurrentDownEvent);
             } else {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index e724547..eedd204 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -413,6 +413,14 @@
     }
 
     /**
+     * Returns whether this input consumer has started touch tracking (if touch tracking is not
+     * deferred).
+     */
+    public boolean hasStartedTouchTracking() {
+        return mInteractionHandler != null;
+    }
+
+    /**
      * Called when the gesture has ended. Does not correlate to the completion of the interaction as
      * the animation can still be running.
      */
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index b70fe8e..41730bb 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -37,6 +37,7 @@
 
 public class OverviewWithoutFocusInputConsumer implements InputConsumer,
         TriggerSwipeUpTouchTracker.OnSwipeUpListener {
+    private static final String TAG = "OverviewWithoutFocusInputConsumer";
 
     private final Context mContext;
     private final InputMonitorCompat mInputMonitor;
@@ -77,7 +78,7 @@
 
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
-        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
+        startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
                 ? mGestureState.getEndTarget().containerType
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index c9c64b6..6dcb7bc 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -53,6 +53,7 @@
 public class ProgressDelegateInputConsumer implements InputConsumer,
         RecentsAnimationCallbacks.RecentsAnimationListener,
         SingleAxisSwipeDetector.Listener {
+    private static final String TAG = "ProgressDelegateInputConsumer";
 
     private static final float SWIPE_DISTANCE_THRESHOLD = 0.2f;
 
@@ -165,7 +166,7 @@
             mRecentsAnimationController.finishController(endToRecents /* toRecents */,
                     null /* callback */, false /* sendUserLeaveHint */);
         } else if (endToRecents) {
-            startHomeIntentSafely(mContext, null);
+            startHomeIntentSafely(mContext, null, TAG);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 49814df..fce188f 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -15,6 +15,9 @@
  */
 package com.android.quickstep.interaction;
 
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
 import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.Utilities.mapBoundToRange;
@@ -47,6 +50,8 @@
 import android.util.Log;
 import android.view.View;
 import android.view.View.AccessibilityDelegate;
+import android.view.Window;
+import android.view.WindowInsetsController;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.widget.ImageView;
@@ -78,6 +83,7 @@
  * for the gestural system navigation.
  */
 public class AllSetActivity extends Activity {
+    private static final String TAG = "AllSetActivity";
 
     private static final String LOG_TAG = "AllSetActivity";
     private static final String URI_SYSTEM_NAVIGATION_SETTING =
@@ -121,6 +127,17 @@
         Resources resources = getResources();
         int mode = resources.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
         boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
+
+        int systemBarsMask = APPEARANCE_LIGHT_STATUS_BARS | APPEARANCE_LIGHT_NAVIGATION_BARS;
+        int systemBarsAppearance = isDarkTheme ? 0 : systemBarsMask;
+        Window window = getWindow();
+        WindowInsetsController insetsController = window == null
+                ? null
+                : window.getInsetsController();
+        if (insetsController != null) {
+            insetsController.setSystemBarsAppearance(systemBarsAppearance, systemBarsMask);
+        }
+
         Intent intent = getIntent();
         int accentColor = intent.getIntExtra(
                 isDarkTheme ? EXTRA_ACCENT_COLOR_DARK_MODE : EXTRA_ACCENT_COLOR_LIGHT_MODE,
@@ -299,7 +316,9 @@
         if (mBackgroundAnimatorListener != null) {
             mAnimatedBackground.removeAnimatorListener(mBackgroundAnimatorListener);
         }
-        dispatchLauncherAnimStartEnd();
+        if (!isChangingConfigurations()) {
+            dispatchLauncherAnimStartEnd();
+        }
     }
 
     private AnimatedFloat createSwipeUpProxy(GestureState state) {
@@ -356,7 +375,7 @@
         @Override
         public boolean performAccessibilityAction(View host, int action, Bundle args) {
             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
-                startHomeIntentSafely(AllSetActivity.this, null);
+                startHomeIntentSafely(AllSetActivity.this, null, TAG);
                 finish();
                 return true;
             }
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index d102502..a36f501 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -19,6 +19,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.text.TextUtils;
@@ -26,6 +27,7 @@
 import android.view.Display;
 import android.view.View;
 import android.view.Window;
+import android.view.WindowInsets;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -41,6 +43,7 @@
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 import com.android.quickstep.util.TISBindHelper;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 
 /** Shows the gesture interactive sandbox in full screen mode. */
@@ -101,8 +104,11 @@
             correctUserOrientation();
         }
         mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
+
+        initWindowInsets();
     }
 
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -113,6 +119,42 @@
         }
     }
 
+    private void initWindowInsets() {
+        View root = findViewById(android.R.id.content);
+        root.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                updateExclusionRects(root);
+            }
+        });
+
+        // Return CONSUMED if you don't want want the window insets to keep being
+        // passed down to descendant views.
+        root.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+            @Override
+            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+                return WindowInsets.CONSUMED;
+            }
+        });
+    }
+
+    private void updateExclusionRects(View rootView) {
+        Insets gestureInsets = rootView.getRootWindowInsets()
+                .getInsets(WindowInsets.Type.systemGestures());
+        ArrayList<Rect> exclusionRects = new ArrayList<>();
+        // Add rect for left
+        exclusionRects.add(new Rect(0, 0, gestureInsets.left, rootView.getHeight()));
+        // Add rect for right
+        exclusionRects.add(new Rect(
+                rootView.getWidth() - gestureInsets.right,
+                0,
+                rootView.getWidth(),
+                rootView.getHeight()
+        ));
+        rootView.setSystemGestureExclusionRects(exclusionRects);
+    }
+
     /**
      * Gesture animations are only in landscape for large screens and portrait for mobile. This
      * method enforces the following flows:
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index ee72144..4359294 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -147,24 +147,15 @@
                     break;
                 case TASK_APPEARED:
                     errorDetected |= printErrorIfTrue(
-                            !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK),
-                            prefix,
-                            /* errorMessage= */ "onTasksAppeared called "
-                                    + "before/without setting end target to new task",
-                            writer);
-                    errorDetected |= printErrorIfTrue(
                             !encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED),
                             prefix,
                             /* errorMessage= */ "onTasksAppeared was not expected to be called",
                             writer);
-                    break;
-                case EXPECTING_TASK_APPEARED:
-                    errorDetected |= printErrorIfTrue(
-                            !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK),
-                            prefix,
-                            /* errorMessage= */ "expecting onTasksAppeared to be called "
-                                    + "before/without setting end target to new task",
-                            writer);
+                    if (encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED)) {
+                        // Remove both events so that we can properly detect following errors.
+                        encounteredEvents.remove(GestureEvent.EXPECTING_TASK_APPEARED);
+                        encounteredEvents.remove(GestureEvent.TASK_APPEARED);
+                    }
                     break;
                 case LAUNCHER_DESTROYED:
                     errorDetected |= printErrorIfTrue(
@@ -282,6 +273,7 @@
                 case START_RECENTS_ANIMATION:
                     lastStartRecentAnimationEventEntryTime = eventEntry.getTime();
                     break;
+                case EXPECTING_TASK_APPEARED:
                 case MOTION_DOWN:
                 case SET_END_TARGET:
                 case SET_END_TARGET_HOME:
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
index 91b53c7..cb44a1a 100644
--- a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -25,6 +25,8 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 
+import androidx.annotation.Nullable;
+
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.SimpleOrientationTouchTransformer;
 import com.android.systemui.shared.system.InputConsumerController;
@@ -42,7 +44,10 @@
     private final Context mContext;
     private final Supplier<Integer> mRotationSupplier;
     private final InputConsumerController mInputConsumerController;
-    private Runnable mCallback;
+
+    /** Called if a new InputConsumer is created via touch down event. */
+    private @Nullable Runnable mOnTouchDownCallback;
+
     private Supplier<InputConsumer> mConsumerSupplier;
 
     // The consumer is created lazily on demand.
@@ -54,11 +59,11 @@
 
     public InputConsumerProxy(Context context, Supplier<Integer> rotationSupplier,
             InputConsumerController inputConsumerController,
-            Runnable callback, Supplier<InputConsumer> consumerSupplier) {
+            Runnable onTouchDownCallback, Supplier<InputConsumer> consumerSupplier) {
         mContext = context;
         mRotationSupplier = rotationSupplier;
         mInputConsumerController = inputConsumerController;
-        mCallback = callback;
+        mOnTouchDownCallback = onTouchDownCallback;
         mConsumerSupplier = consumerSupplier;
     }
 
@@ -82,7 +87,7 @@
                 onInputConsumerMotionEvent(event);
             }
         } else if (ev instanceof KeyEvent) {
-            initInputConsumerIfNeeded();
+            initInputConsumerIfNeeded(/* isFromTouchDown= */ false);
             mInputConsumer.onKeyEvent((KeyEvent) ev);
             return true;
         }
@@ -105,7 +110,7 @@
 
         if (action == ACTION_DOWN) {
             mTouchInProgress = true;
-            initInputConsumerIfNeeded();
+            initInputConsumerIfNeeded(/* isFromTouchDown= */ true);
         } else if (action == ACTION_CANCEL || action == ACTION_UP) {
             // Finish any pending actions
             mTouchInProgress = false;
@@ -123,7 +128,7 @@
     }
 
     private void onInputConsumerHoverEvent(MotionEvent ev) {
-        initInputConsumerIfNeeded();
+        initInputConsumerIfNeeded(/* isFromTouchDown= */ false);
         if (mInputConsumer != null) {
             SimpleOrientationTouchTransformer.INSTANCE.get(mContext).transform(ev,
                     mRotationSupplier.get());
@@ -141,14 +146,15 @@
         mInputConsumerController.setInputListener(null);
     }
 
-    public void unregisterCallback() {
-        mCallback = null;
+    /** Sets mOnToudhCownCallback = null. */
+    public void unregisterOnTouchDownCallback() {
+        mOnTouchDownCallback = null;
     }
 
-    private void initInputConsumerIfNeeded() {
+    private void initInputConsumerIfNeeded(boolean isFromTouchDown) {
         if (mInputConsumer == null) {
-            if (mCallback != null) {
-                mCallback.run();
+            if (isFromTouchDown && mOnTouchDownCallback != null) {
+                mOnTouchDownCallback.run();
             }
             mInputConsumer = mConsumerSupplier.get();
             mConsumerSupplier = null;
diff --git a/quickstep/src/com/android/quickstep/util/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/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index a4a53d1..5b1d614 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -27,7 +27,6 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -170,7 +169,7 @@
                         (task) -> {
                             setIcon(mIconView2, task.icon);
                             if (enableOverviewIconMenu()) {
-                                setText(mIconView2, TaskUtils.getTitle(getContext(), task));
+                                setText(mIconView2, task.title);
                             }
                             mDigitalWellBeingToast2.initialize(mSecondaryTask);
                             mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 5093f22..ab51a70 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -35,6 +35,7 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 import static com.android.quickstep.TaskOverlayFactory.getEnabledShortcuts;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
 import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
 import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
 
@@ -106,6 +107,7 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TaskViewUtils;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.BorderAnimator;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
@@ -859,6 +861,7 @@
                     getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
+                ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
                 RecentsView recentsView = getRecentsView();
                 if (recentsView.getRunningTaskViewId() != -1) {
                     recentsView.onTaskLaunchedInLiveTileMode();
@@ -1096,7 +1099,7 @@
                         (task) -> {
                             setIcon(mIconView, task.icon);
                             if (enableOverviewIconMenu()) {
-                                setText(mIconView, TaskUtils.getTitle(getContext(), task));
+                                setText(mIconView, task.title);
                             }
                             mDigitalWellBeingToast.initialize(task);
                         });
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactoryTest.kt
index 37fcf43..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 16235e0..17fa253 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -162,7 +162,7 @@
     @Before
     public void setUp() {
         mLauncher.onTestStart();
-        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 f6368b0..c4c95bc 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
@@ -16,6 +16,8 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -25,6 +27,7 @@
 
 import com.android.launcher3.tapl.OverviewTaskMenu;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.util.rule.TestStabilityRule;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -72,6 +75,7 @@
     }
 
     @Test
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/288939273
     public void testSplitTaskTapBothIconMenus() {
         createAndLaunchASplitPair();
 
@@ -92,7 +96,7 @@
         startTestActivity(2);
         startTestActivity(3);
 
-        if (mLauncher.isTablet()) {
+        if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) {
             mLauncher.goHome().switchToOverview().getOverviewActions()
                     .clickSplit()
                     .getTestActivityTask(2)
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index d45c225..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;
@@ -100,7 +103,6 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @PlatinumTest(focusArea = "launcher")
     public void testWorkspaceSwitchToAllApps() {
         assertNotNull("switchToAllApps() returned null",
                 mLauncher.getWorkspace().switchToAllApps());
@@ -177,8 +179,9 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @PlatinumTest(focusArea = "launcher")
     public void testOverviewActions() throws Exception {
+        assumeFalse("Skipping Overview Actions tests for grid only overview",
+                mLauncher.isTablet() && mLauncher.isGridOnlyOverviewEnabled());
         // Experimenting for b/165029151:
         final Overview overview = mLauncher.goHome().switchToOverview();
         if (overview.hasTasks()) overview.dismissAllTasks();
@@ -212,7 +215,6 @@
     @NavigationModeSwitch
     @PortraitLandscape
     @ScreenRecord // b/238461765
-    @PlatinumTest(focusArea = "launcher")
     public void testSwitchToOverview() throws Exception {
         startTestAppsWithCheck();
         assertNotNull("Workspace.switchToOverview() returned null",
@@ -238,7 +240,6 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @PlatinumTest(focusArea = "launcher")
     public void testBackground() throws Exception {
         startAppFast(CALCULATOR_APP_PACKAGE);
         final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
@@ -265,7 +266,6 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @PlatinumTest(focusArea = "launcher")
     public void testQuickSwitchFromApp() throws Exception {
         startTestActivity(2);
         startTestActivity(3);
@@ -295,7 +295,6 @@
 
     @Test
     @ScreenRecord // b/242163205
-    @PlatinumTest(focusArea = "launcher")
     @TaskbarModeSwitch(mode = PERSISTENT)
     public void testQuickSwitchToPreviousAppForTablet() throws Exception {
         assumeTrue(mLauncher.isTablet());
@@ -322,7 +321,6 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @PlatinumTest(focusArea = "launcher")
     public void testQuickSwitchFromHome() throws Exception {
         startTestActivity(2);
         mLauncher.goHome().quickSwitchToPreviousApp();
@@ -334,7 +332,6 @@
     @Test
     @PortraitLandscape
     @NavigationModeSwitch
-    @PlatinumTest(focusArea = "launcher")
     public void testPressBack() throws Exception {
         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 READ_DEVICE_CONFIG_PERMISSION);
@@ -354,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());
 
@@ -377,7 +374,9 @@
         // Test opening the task.
         overview.getCurrentTask().open();
         assertTrue("Test activity didn't open from Overview",
-                mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity10")),
+                mDevice.wait(Until.hasObject(By.pkg(getAppPackageName()).text(
+                                mLauncher.isGridOnlyOverviewEnabled() ? "TestActivity12"
+                                        : "TestActivity13")),
                         DEFAULT_UI_TIMEOUT));
 
         // Scroll the task offscreen as it is now first
@@ -398,16 +397,17 @@
                 (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
                         launcher)) <= 1)));
 
-        // Test dismissing more tasks.
-        assertTrue("Launcher internal state didn't remain in Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        overview.getCurrentTask().dismiss();
-        assertTrue("Launcher internal state didn't remain in Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        overview.getCurrentTask().dismiss();
-        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals",
-                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-                        launcher)) <= 1)));
+        // TODO(b/308841019): Re-enable after fixing Overview jank when dismiss
+//        // Test dismissing more tasks.
+//        assertTrue("Launcher internal state didn't remain in Overview",
+//                isInState(() -> LauncherState.OVERVIEW));
+//        overview.getCurrentTask().dismiss();
+//        assertTrue("Launcher internal state didn't remain in Overview",
+//                isInState(() -> LauncherState.OVERVIEW));
+//        overview.getCurrentTask().dismiss();
+//        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple dismissals",
+//                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
+//                        launcher)) <= 1)));
 
         // Test dismissing all tasks.
         mLauncher.goHome().switchToOverview().dismissAllTasks();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index acbb58f..d3bf56f 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -108,7 +108,6 @@
     }
 
     @Test
-    @Ignore("Enable once App Pairs flagged on. These cause memory leaks b/297135374")
     public void testSaveAppPairMenuItemExistsOnSplitPair() throws Exception {
         assumeTrue("App pairs feature is currently not enabled, no test needed",
                 Flags.enableAppPairs());
@@ -124,7 +123,6 @@
     }
 
     @Test
-    @Ignore("Enable once App Pairs flagged on. These cause memory leaks b/297135374")
     public void testSaveAppPairMenuItemDoesNotExistOnSingleTask() throws Exception {
         assumeTrue("App pairs feature is currently not enabled, no test needed",
                 Flags.enableAppPairs());
@@ -143,7 +141,7 @@
         startTestActivity(2);
         startTestActivity(3);
 
-        if (mLauncher.isTablet()) {
+        if (mLauncher.isTablet() && !mLauncher.isGridOnlyOverviewEnabled()) {
             mLauncher.goHome().switchToOverview().getOverviewActions()
                     .clickSplit()
                     .getTestActivityTask(2)
diff --git a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index a54dc2d..b365173 100644
--- a/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -18,11 +18,15 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.ArrayMap;
+import android.util.DisplayMetrics;
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 
@@ -35,7 +39,6 @@
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.LauncherModelHelper;
 import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.ReflectionHelpers;
 import com.android.launcher3.util.RotationUtils;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
@@ -61,6 +64,7 @@
     public void taskProperlyScaled_portrait_noRotation_sameInsets1() {
         new TaskMatrixVerifier()
                 .withLauncherSize(1200, 2450)
+                .withDensityDpi(420)
                 .withInsets(new Rect(0, 80, 0, 120))
                 .verifyNoTransforms();
     }
@@ -69,6 +73,7 @@
     public void taskProperlyScaled_portrait_noRotation_sameInsets2() {
         new TaskMatrixVerifier()
                 .withLauncherSize(1200, 2450)
+                .withDensityDpi(420)
                 .withInsets(new Rect(55, 80, 55, 120))
                 .verifyNoTransforms();
     }
@@ -77,6 +82,7 @@
     public void taskProperlyScaled_landscape_noRotation_sameInsets1() {
         new TaskMatrixVerifier()
                 .withLauncherSize(2450, 1250)
+                .withDensityDpi(420)
                 .withInsets(new Rect(0, 80, 0, 40))
                 .verifyNoTransforms();
     }
@@ -85,6 +91,7 @@
     public void taskProperlyScaled_landscape_noRotation_sameInsets2() {
         new TaskMatrixVerifier()
                 .withLauncherSize(2450, 1250)
+                .withDensityDpi(420)
                 .withInsets(new Rect(0, 80, 120, 0))
                 .verifyNoTransforms();
     }
@@ -93,6 +100,7 @@
     public void taskProperlyScaled_landscape_noRotation_sameInsets3() {
         new TaskMatrixVerifier()
                 .withLauncherSize(2450, 1250)
+                .withDensityDpi(420)
                 .withInsets(new Rect(55, 80, 55, 120))
                 .verifyNoTransforms();
     }
@@ -101,6 +109,7 @@
     public void taskProperlyScaled_landscape_rotated() {
         new TaskMatrixVerifier()
                 .withLauncherSize(1200, 2450)
+                .withDensityDpi(420)
                 .withInsets(new Rect(0, 80, 0, 120))
                 .withAppBounds(
                         new Rect(0, 0, 2450, 1200),
@@ -112,6 +121,7 @@
     private static class TaskMatrixVerifier extends TransformParams {
 
         private Point mDisplaySize = new Point();
+        private int mDensityDpi = DisplayMetrics.DENSITY_DEFAULT;
         private Rect mDisplayInsets = new Rect();
         private Rect mAppBounds = new Rect();
         private Rect mLauncherInsets = new Rect();
@@ -129,6 +139,11 @@
             return this;
         }
 
+        TaskMatrixVerifier withDensityDpi(int densityDpi) {
+            mDensityDpi = densityDpi;
+            return this;
+        }
+
         TaskMatrixVerifier withInsets(Rect insets) {
             mDisplayInsets.set(insets);
             mLauncherInsets.set(insets);
@@ -173,13 +188,17 @@
                         new ArrayMap<>();
                 perDisplayBoundsCache.put(cdi.normalize(), allBounds);
 
-                DisplayController.Info mockInfo = new Info(
-                        helper.sandboxContext, wmProxy, perDisplayBoundsCache);
+                Configuration configuration = new Configuration();
+                configuration.densityDpi = mDensityDpi;
+                Context configurationContext = helper.sandboxContext.createConfigurationContext(
+                        configuration);
 
-                DisplayController controller =
-                        DisplayController.INSTANCE.get(helper.sandboxContext);
-                controller.close();
-                ReflectionHelpers.setField(controller, "mInfo", mockInfo);
+                DisplayController.Info info = new Info(
+                        configurationContext, wmProxy, perDisplayBoundsCache);
+
+                DisplayController mockController = mock(DisplayController.class);
+                when(mockController.getInfo()).thenReturn(info);
+                helper.sandboxContext.putObject(DisplayController.INSTANCE, mockController);
 
                 mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext)
                         .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation);
@@ -189,7 +208,7 @@
                         FallbackActivityInterface.INSTANCE);
                 tvs.setDp(mDeviceProfile);
 
-                int launcherRotation = mockInfo.rotation;
+                int launcherRotation = info.rotation;
                 if (mAppRotation < 0) {
                     mAppRotation = launcherRotation;
                 }
diff --git a/res/drawable/bg_ps_header.xml b/res/drawable/bg_ps_header.xml
new file mode 100644
index 0000000..526bb5a
--- /dev/null
+++ b/res/drawable/bg_ps_header.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/ps_container_corner_radius" />
+    <solid android:color="?attr/materialColorSurfaceContainerHigh" />
+</shape>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_lock_button.xml b/res/drawable/bg_ps_lock_button.xml
new file mode 100644
index 0000000..aef1e81
--- /dev/null
+++ b/res/drawable/bg_ps_lock_button.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/ps_lock_button_width"
+    android:height="@dimen/ps_button_height"
+    android:viewportWidth="89"
+    android:viewportHeight="36">
+    <path
+        android:pathData="M18,0L71,0A18,18 0,0 1,89 18L89,18A18,18 0,0 1,71 36L18,36A18,18 0,0 1,0 18L0,18A18,18 0,0 1,18 0z"
+        android:fillColor="?attr/materialColorPrimaryFixedDim"/>
+    <path
+        android:pathData="M26.167,14.667H27C27.917,14.667 28.667,15.417 28.667,16.333V24.667C28.667,25.583 27.917,26.333 27,26.333H17C16.083,26.333 15.333,25.583 15.333,24.667V16.333C15.333,15.417 16.083,14.667 17,14.667H17.833V13C17.833,10.7 19.7,8.833 22,8.833C24.3,8.833 26.167,10.7 26.167,13V14.667ZM22,10.5C20.617,10.5 19.5,11.617 19.5,13V14.667H24.5V13C24.5,11.617 23.383,10.5 22,10.5ZM17,24.667V16.333H27V24.667H17ZM23.667,20.5C23.667,21.417 22.917,22.167 22,22.167C21.083,22.167 20.333,21.417 20.333,20.5C20.333,19.583 21.083,18.833 22,18.833C22.917,18.833 23.667,19.583 23.667,20.5Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"
+        android:fillType="evenOdd"/>
+    <path
+        android:pathData="M41.204,23V12.976H42.73V21.544H47.504V23H41.204ZM52.352,23.224C51.615,23.224 50.976,23.061 50.434,22.734C49.893,22.398 49.473,21.936 49.174,21.348C48.885,20.76 48.74,20.083 48.74,19.318C48.74,18.543 48.885,17.867 49.174,17.288C49.473,16.7 49.893,16.243 50.434,15.916C50.976,15.58 51.615,15.412 52.352,15.412C53.099,15.412 53.738,15.58 54.27,15.916C54.812,16.243 55.227,16.7 55.516,17.288C55.815,17.867 55.964,18.543 55.964,19.318C55.964,20.083 55.815,20.76 55.516,21.348C55.227,21.936 54.812,22.398 54.27,22.734C53.738,23.061 53.099,23.224 52.352,23.224ZM52.352,21.838C52.772,21.838 53.141,21.74 53.458,21.544C53.776,21.348 54.023,21.063 54.2,20.69C54.378,20.307 54.466,19.85 54.466,19.318C54.466,18.777 54.378,18.319 54.2,17.946C54.023,17.573 53.776,17.288 53.458,17.092C53.141,16.896 52.777,16.798 52.366,16.798C51.946,16.798 51.578,16.896 51.26,17.092C50.943,17.288 50.691,17.573 50.504,17.946C50.327,18.319 50.238,18.777 50.238,19.318C50.238,19.859 50.327,20.317 50.504,20.69C50.691,21.063 50.943,21.348 51.26,21.544C51.587,21.74 51.951,21.838 52.352,21.838ZM60.899,23.224C60.199,23.224 59.583,23.065 59.051,22.748C58.528,22.421 58.118,21.964 57.819,21.376C57.529,20.788 57.385,20.102 57.385,19.318C57.385,18.525 57.534,17.839 57.833,17.26C58.141,16.672 58.561,16.219 59.093,15.902C59.634,15.575 60.255,15.412 60.955,15.412C61.832,15.412 62.556,15.631 63.125,16.07C63.694,16.509 64.039,17.111 64.161,17.876L62.705,18.114C62.611,17.713 62.411,17.395 62.103,17.162C61.804,16.919 61.412,16.798 60.927,16.798C60.544,16.798 60.199,16.896 59.891,17.092C59.583,17.279 59.335,17.559 59.149,17.932C58.972,18.305 58.883,18.767 58.883,19.318C58.883,19.859 58.972,20.321 59.149,20.704C59.326,21.077 59.569,21.362 59.877,21.558C60.185,21.745 60.535,21.838 60.927,21.838C61.394,21.838 61.771,21.721 62.061,21.488C62.36,21.255 62.579,20.909 62.719,20.452L64.133,20.788C63.956,21.507 63.596,22.095 63.055,22.552C62.514,23 61.795,23.224 60.899,23.224ZM65.985,23V12.136H67.483V18.688L70.381,15.636H72.187V15.72L69.499,18.492L72.257,22.916V23H70.549L68.435,19.598L67.483,20.564V23H65.985Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_settings_button.xml b/res/drawable/bg_ps_settings_button.xml
new file mode 100644
index 0000000..c06e0c0
--- /dev/null
+++ b/res/drawable/bg_ps_settings_button.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/ps_button_height"
+    android:height="@dimen/ps_button_height"
+    android:viewportWidth="40"
+    android:viewportHeight="40">
+    <path
+        android:pathData="M20,0L20,0A20,20 0,0 1,40 20L40,20A20,20 0,0 1,20 40L20,40A20,20 0,0 1,0 20L0,20A20,20 0,0 1,20 0z"
+        android:fillColor="?attr/materialColorSurfaceBright"/>
+    <group>
+        <clip-path
+            android:pathData="M10,10h20v20h-20z"/>
+        <path
+            android:pathData="M21.542,28.542H18.458C17.841,28.542 17.325,28.092 17.25,27.483L17.025,25.908C16.8,25.792 16.583,25.667 16.367,25.525L14.866,26.125C14.283,26.342 13.642,26.1 13.358,25.583L11.833,22.942C11.542,22.392 11.667,21.742 12.133,21.375L13.408,20.383C13.4,20.258 13.392,20.133 13.392,20C13.392,19.875 13.4,19.742 13.408,19.617L12.142,18.625C11.65,18.25 11.525,17.575 11.833,17.058L13.375,14.4C13.658,13.883 14.3,13.65 14.866,13.875L16.375,14.483C16.591,14.342 16.808,14.217 17.025,14.1L17.25,12.508C17.325,11.925 17.841,11.467 18.45,11.467H21.533C22.15,11.467 22.667,11.917 22.742,12.525L22.966,14.1C23.191,14.217 23.408,14.342 23.625,14.483L25.125,13.883C25.716,13.667 26.358,13.908 26.642,14.425L28.175,17.075C28.475,17.625 28.341,18.275 27.875,18.642L26.608,19.633C26.617,19.758 26.625,19.883 26.625,20.017C26.625,20.15 26.617,20.275 26.608,20.4L27.875,21.392C28.341,21.767 28.475,22.417 28.183,22.942L26.633,25.625C26.35,26.142 25.708,26.375 25.133,26.15L23.633,25.55C23.417,25.692 23.2,25.817 22.983,25.933L22.758,27.525C22.675,28.092 22.158,28.542 21.542,28.542ZM21.1,27.267C21.1,27.275 21.1,27.275 21.1,27.283V27.267ZM18.9,27.25V27.267C18.908,27.267 18.908,27.258 18.9,27.25ZM18.85,26.875H21.15L21.458,24.75L21.9,24.567C22.267,24.417 22.633,24.2 23.017,23.917L23.392,23.633L25.375,24.433L26.525,22.433L24.833,21.117L24.892,20.65C24.917,20.433 24.941,20.225 24.941,20C24.941,19.775 24.917,19.558 24.892,19.35L24.833,18.883L26.525,17.567L25.367,15.567L23.375,16.367L23,16.075C22.65,15.808 22.275,15.592 21.892,15.433L21.458,15.25L21.15,13.125H18.85L18.542,15.25L18.1,15.425C17.733,15.583 17.367,15.792 16.983,16.083L16.608,16.358L14.625,15.567L13.467,17.558L15.158,18.875L15.1,19.342C15.075,19.558 15.05,19.783 15.05,20C15.05,20.217 15.066,20.442 15.1,20.65L15.158,21.117L13.467,22.433L14.616,24.433L16.608,23.633L16.983,23.925C17.341,24.2 17.7,24.408 18.091,24.567L18.533,24.75L18.85,26.875ZM25.183,24.767C25.183,24.775 25.175,24.783 25.175,24.792L25.183,24.767ZM14.808,24.758L14.816,24.775C14.816,24.767 14.808,24.758 14.808,24.758ZM25.183,15.225C25.183,15.233 25.191,15.242 25.191,15.242L25.183,15.225ZM14.825,15.208L14.816,15.225C14.816,15.225 14.825,15.217 14.825,15.208ZM21.091,12.733C21.091,12.742 21.091,12.742 21.091,12.75V12.733ZM18.908,12.717V12.733C18.908,12.725 18.908,12.725 18.908,12.717Z"
+            android:fillColor="?attr/materialColorOnSurfaceVariant"/>
+        <path
+            android:pathData="M20,22.917C21.611,22.917 22.916,21.611 22.916,20C22.916,18.389 21.611,17.083 20,17.083C18.389,17.083 17.083,18.389 17.083,20C17.083,21.611 18.389,22.917 20,22.917Z"
+            android:fillColor="?attr/materialColorOnSurfaceVariant"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_transition_image.xml b/res/drawable/bg_ps_transition_image.xml
new file mode 100644
index 0000000..dfad3cf
--- /dev/null
+++ b/res/drawable/bg_ps_transition_image.xml
@@ -0,0 +1,35 @@
+<!--
+  ~ 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.
+  -->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:name="vector"
+    android:width="@dimen/ps_button_height"
+    android:height="@dimen/ps_button_height"
+    android:viewportWidth="40"
+    android:viewportHeight="40">
+    <path
+        android:name="path"
+        android:pathData="M 19.998 36.668 C 10.816 36.668 3.332 29.184 3.332 20 C 3.332 10.818 10.816 3.334 19.998 3.334 C 20.916 3.334 21.666 4.084 21.666 5 C 21.666 5.918 20.916 6.668 19.998 6.668 C 12.648 6.668 6.666 12.65 6.666 20 C 6.666 27.35 12.648 33.334 19.998 33.334 C 27.348 33.334 33.332 27.35 33.332 20 C 33.332 19.084 34.082 18.334 34.998 18.334 C 35.916 18.334 36.666 19.084 36.666 20 C 36.666 29.184 29.182 36.668 19.998 36.668 Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"/>
+    <path
+        android:name="path_3"
+        android:pathData="M 20 0 C 25.302 0 30.393 2.109 34.142 5.858 C 37.891 9.607 40 14.698 40 20 C 40 25.302 37.891 30.393 34.142 34.142 C 30.393 37.891 25.302 40 20 40 C 14.698 40 9.607 37.891 5.858 34.142 C 2.109 30.393 0 25.302 0 20 C 0 14.698 2.109 9.607 5.858 5.858 C 9.607 2.109 14.698 0 20 0"
+        android:fillColor="?attr/materialColorPrimaryFixedDim"/>
+    <path
+        android:name="path_4"
+        android:pathData="M 19.999 28.334 C 15.408 28.334 11.666 24.592 11.666 20 C 11.666 15.409 15.408 11.667 19.999 11.667 C 20.458 11.667 20.833 12.042 20.833 12.5 C 20.833 12.959 20.458 13.334 19.999 13.334 C 16.324 13.334 13.333 16.325 13.333 20 C 13.333 23.675 16.324 26.667 19.999 26.667 C 23.674 26.667 26.666 23.675 26.666 20 C 26.666 19.542 27.041 19.167 27.499 19.167 C 27.958 19.167 28.333 19.542 28.333 20 C 28.333 24.592 24.591 28.334 19.999 28.334 Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"/>
+</vector>
\ No newline at end of file
diff --git a/res/drawable/bg_ps_unlock_button.xml b/res/drawable/bg_ps_unlock_button.xml
new file mode 100644
index 0000000..d5eedd2
--- /dev/null
+++ b/res/drawable/bg_ps_unlock_button.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/ps_button_height"
+    android:height="@dimen/ps_button_height"
+    android:viewportWidth="36"
+    android:viewportHeight="36">
+    <path
+        android:pathData="M18,0L18,0A18,18 0,0 1,36 18L36,18A18,18 0,0 1,18 36L18,36A18,18 0,0 1,0 18L0,18A18,18 0,0 1,18 0z"
+        android:fillColor="?attr/materialColorPrimaryFixedDim"/>
+    <path
+        android:pathData="M22.167,14.667H23C23.917,14.667 24.667,15.417 24.667,16.333V24.667C24.667,25.583 23.917,26.333 23,26.333H13C12.083,26.333 11.333,25.583 11.333,24.667V16.333C11.333,15.417 12.083,14.667 13,14.667H13.833V13C13.833,10.7 15.7,8.833 18,8.833C20.3,8.833 22.167,10.7 22.167,13V14.667ZM18,10.5C16.617,10.5 15.5,11.617 15.5,13V14.667H20.5V13C20.5,11.617 19.383,10.5 18,10.5ZM13,24.667V16.333H23V24.667H13ZM19.667,20.5C19.667,21.417 18.917,22.167 18,22.167C17.083,22.167 16.333,21.417 16.333,20.5C16.333,19.583 17.083,18.833 18,18.833C18.917,18.833 19.667,19.583 19.667,20.5Z"
+        android:fillColor="?attr/materialColorOnPrimaryFixed"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/res/layout/private_space_header.xml b/res/layout/private_space_header.xml
new file mode 100644
index 0000000..24e290d
--- /dev/null
+++ b/res/layout/private_space_header.xml
@@ -0,0 +1,71 @@
+<?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.
+  -->
+
+<RelativeLayout
+        android:id="@+id/ps_header_layout"
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/ps_header_height"
+        android:background="@drawable/bg_ps_header"
+        android:clipToOutline="true"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+
+    <ImageButton
+        android:id="@+id/ps_lock_unlock_button"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ps_header_image_height"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:background="@android:color/transparent"
+        android:layout_marginEnd="@dimen/ps_header_layout_margin"
+        android:contentDescription="@string/ps_container_lock_unlock_button" />
+
+    <ImageButton
+        android:id="@+id/ps_settings_button"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ps_header_image_height"
+        android:layout_toStartOf="@+id/ps_lock_unlock_button"
+        android:layout_centerVertical="true"
+        android:background="@android:color/transparent"
+        android:layout_marginEnd="@dimen/ps_header_settings_icon_margin_end"
+        android:src="@drawable/bg_ps_settings_button"
+        android:contentDescription="@string/ps_container_settings" />
+
+    <ImageView
+        android:id="@+id/ps_transition_image"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ps_header_image_height"
+        android:layout_alignParentEnd="true"
+        android:layout_centerVertical="true"
+        android:background="@android:color/transparent"
+        android:layout_marginEnd="@dimen/ps_header_layout_margin"
+        android:src="@drawable/bg_ps_transition_image"
+        android:contentDescription="@string/ps_container_transition" />
+
+    <TextView
+        android:id="@+id/ps_container_header"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/ps_header_text_height"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:gravity="center_vertical"
+        android:layout_marginStart="@dimen/ps_header_layout_margin"
+        android:text="@string/ps_container_title"
+        android:theme="@style/PrivateSpaceHeaderTextStyle"/>
+
+</RelativeLayout>
\ No newline at end of file
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 39f1477..88aeab5 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -133,7 +133,7 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ವಿಜೆಟ್ ಪಟ್ಟಿಯನ್ನು ಮುಚ್ಚಲಾಗಿದೆ"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಸೇರಿಸಿ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ಐಟಂ ಇಲ್ಲಿಗೆ ಸರಿಸಿ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ಮುಖಪುಟ ಪರದೆಗೆ ಐಟಂ ಸೇರಿಸಲಾಗಿದೆ"</string>
+    <string name="item_added_to_workspace" msgid="4211073925752213539">"ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಐಟಂ ಸೇರಿಸಲಾಗಿದೆ"</string>
     <string name="item_removed" msgid="851119963877842327">"ಐಟಂ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
     <string name="undo" msgid="4151576204245173321">"ರದ್ದುಮಾಡಿ"</string>
     <string name="action_move" msgid="4339390619886385032">"ಐಟಂ ಸರಿಸಿ"</string>
@@ -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..ac701d6 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>
@@ -455,4 +456,17 @@
     <!-- Default Ime height. Used only for logging purposes.
     Assume this is default keyboard height in EN locale in case the keyboard height is not known when queried.-->
     <dimen name="default_ime_height">300dp</dimen>
+
+    <!-- Private Space parameters -->
+    <dimen name="ps_container_corner_radius">24dp</dimen>
+    <dimen name="ps_header_height">64dp</dimen>
+    <dimen name="ps_header_relative_layout_height">48dp</dimen>
+    <dimen name="ps_header_image_height">36dp</dimen>
+    <dimen name="ps_header_text_height">24dp</dimen>
+    <dimen name="ps_header_layout_margin">16dp</dimen>
+    <dimen name="ps_header_settings_icon_margin_end">8dp</dimen>
+    <dimen name="ps_header_text_size">16sp</dimen>
+    <dimen name="ps_button_height">36dp</dimen>
+    <dimen name="ps_button_width">36dp</dimen>
+    <dimen name="ps_lock_button_width">89dp</dimen>
 </resources>
diff --git a/res/values/id.xml b/res/values/id.xml
index 872ae2f..6156c91 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -45,4 +45,10 @@
 
     <item type="id" name="dismiss_view" />
 
+    <!-- Private Space parameters -->
+    <item type="id" name="ps_container_header" />
+    <item type="id" name="ps_lock_unlock_button" />
+    <item type="id" name="ps_settings_button" />
+    <item type="id" name="ps_transition_image" />
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4e865d0..31579cd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -454,6 +454,19 @@
     <!-- Failed action error message: e.g. Failed: Pause -->
     <string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
 
+    <!-- Strings for Private Space -->
     <!-- Private space label -->
     <string name="private_space_label">Private space</string>
+    <!-- Title for Private Space Container shown at the bottom of all apps drawer -->
+    <string name="ps_container_title">Private</string>
+    <!-- Description for Private Space Settings button -->
+    <string name="ps_container_settings">Private Space Settings</string>
+    <!-- Description for Private Space Lock/Unlock button -->
+    <string name="ps_container_lock_unlock_button">Lock/Unlock Private Space</string>
+    <!-- Description for Private Space Transition button -->
+    <string name="ps_container_transition">Private Space Transitioning</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/res/values/styles.xml b/res/values/styles.xml
index 82a227a..36991b1 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -435,4 +435,10 @@
         <item name="arrowTipBackground">@color/arrow_tip_view_bg</item>
         <item name="arrowTipTextColor">@color/arrow_tip_view_content</item>
     </style>
+
+    <style name="PrivateSpaceHeaderTextStyle">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">@color/material_color_on_surface</item>
+        <item name="android:fontFamily">google-sans-text-medium</item>
+    </style>
 </resources>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 189db21..79b831e 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -32,6 +32,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
 
+import com.android.launcher3.LauncherConstants.ActivityCodes;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
@@ -270,7 +271,7 @@
                         .startConfigActivity(
                                 mLauncher,
                                 mWidgetView.getAppWidgetId(),
-                                Launcher.REQUEST_RECONFIGURE_APPWIDGET);
+                                ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET);
             });
             if (!hasSeenReconfigurableWidgetEducationTip()) {
                 post(() -> {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ddcb1e6..e2e528c 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -294,7 +294,7 @@
         }
     }
 
-    private void animateDotScale(float... dotScales) {
+    public void animateDotScale(float... dotScales) {
         cancelDotScaleAnim();
         mDotScaleAnim = ObjectAnimator.ofFloat(this, DOT_SCALE_PROPERTY, dotScales);
         mDotScaleAnim.addListener(new AnimatorListenerAdapter() {
@@ -646,7 +646,7 @@
         return mForceHideDot;
     }
 
-    private boolean hasDot() {
+    public boolean hasDot() {
         return mDotInfo != null;
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 73cd8c4..28c7a37 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,46 @@
         // Needs to be calculated after hotseatBarSizePx is correct,
         // for the available height to be correct
         if (mIsResponsiveGrid) {
-            WorkspaceSpecs workspaceSpecs = WorkspaceSpecs.create(
-                    new ResourceHelper(context,
-                            isTwoPanels ? inv.workspaceSpecsTwoPanelId : inv.workspaceSpecsId));
             int availableResponsiveWidth =
                     availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0);
-            int numColumns = getPanelCount() * inv.numColumns;
+            int numWorkspaceColumns = getPanelCount() * inv.numColumns;
             // don't use availableHeightPx because it subtracts mInsets.bottom
             int availableResponsiveHeight = heightPx - mInsets.top
-                            - (isVerticalBarLayout() ? 0 : hotseatBarSizePx);
-            mResponsiveWidthSpec = workspaceSpecs.getCalculatedWidthSpec(numColumns,
-                    availableResponsiveWidth);
-            mResponsiveHeightSpec = workspaceSpecs.getCalculatedHeightSpec(inv.numRows,
-                    availableResponsiveHeight);
+                    - (isVerticalBarLayout() ? 0 : hotseatBarSizePx);
+            float responsiveAspectRatio = (float) widthPx / heightPx;
 
-            AllAppsSpecs allAppsSpecs = AllAppsSpecs.create(
+            ResponsiveSpecsProvider workspaceSpecs = ResponsiveSpecsProvider.create(
                     new ResourceHelper(context,
-                            isTwoPanels ? inv.allAppsSpecsTwoPanelId : inv.allAppsSpecsId));
-            mAllAppsResponsiveWidthSpec = allAppsSpecs.getCalculatedWidthSpec(numColumns,
-                    mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
-            mAllAppsResponsiveHeightSpec = allAppsSpecs.getCalculatedHeightSpec(inv.numRows,
-                    mResponsiveHeightSpec.getAvailableSpace(), mResponsiveHeightSpec);
+                            isTwoPanels ? inv.workspaceSpecsTwoPanelId : inv.workspaceSpecsId),
+                    ResponsiveSpecType.Workspace);
+            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, numShownAllAppsColumns, 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 +758,7 @@
 
     private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
         if (mIsResponsiveGrid) {
-            return mResponsiveWidthSpec.getStartPaddingPx();
+            return mResponsiveWorkspaceWidthSpec.getStartPaddingPx();
         }
 
         if (isVerticalBarLayout()) {
@@ -761,8 +774,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 +871,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 +1017,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 +1059,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 +1164,7 @@
 
         // All apps
         if (mIsResponsiveGrid) {
-            updateAllAppsWithResponsiveMeasures();
+            updateAllAppsWithResponsiveMeasures(res);
         } else {
             updateAllAppsIconSize(scale, res);
         }
@@ -1246,20 +1263,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 +1338,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 +1396,6 @@
 
         if (mIsResponsiveGrid) {
             folderCellWidthPx = mResponsiveFolderWidthSpec.getCellSizePx();
-
-            // Height
             folderCellHeightPx = mResponsiveFolderHeightSpec.getCellSizePx();
             folderContentPaddingTop = mResponsiveFolderHeightSpec.getStartPaddingPx();
             folderFooterHeightPx = mResponsiveFolderHeightSpec.getEndPaddingPx();
@@ -1356,24 +1412,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 +1587,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 +2119,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/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 8ec5c18..8eab3e3 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -90,19 +90,34 @@
     /**
      * Synchronously shows the soft input method.
      *
-     * @param shouldFocus whether this EditText should also request focus.
-     * @return true if the keyboard is shown correctly and focus is given to this view (if
-     *     applicable).
+     * @return true if the keyboard is shown correctly and focus is given to this view.
      */
-    public boolean showKeyboard(boolean shouldFocus) {
+    public boolean showKeyboard() {
         onKeyboardShown();
-        boolean focusResult = !shouldFocus || requestFocus();
-        return focusResult && showSoftInputInternal();
+        return requestFocus() && showSoftInputInternal();
+    }
+
+    /**
+     * Requests the framework to show the keyboard in order to ensure that an already registered
+     * controlled keyboard animation is triggered correctly.
+     * Must NEVER be called in any other case than to trigger a pre-registered controlled animation.
+     */
+    public void requestShowKeyboardForControlledAnimation() {
+        // We don't log the keyboard state, as that must happen only after the controlled animation
+        // has completed.
+        // We also must not request focus, as this triggers unwanted side effects.
+        showSoftInputInternal();
     }
 
     public void hideKeyboard() {
+        hideKeyboard(/* clearFocus= */ true);
+    }
+
+    public void hideKeyboard(boolean clearFocus) {
         ActivityContext.lookupContext(getContext()).hideKeyboard();
-        clearFocus();
+        if (clearFocus) {
+            clearFocus();
+        }
     }
 
     protected void onKeyboardShown() {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index edff48b..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() {
@@ -2121,7 +2109,7 @@
 
     @Override
     public void bindScreens(IntArray orderedScreenIds) {
-        mWorkspace.mPageIndicator.setAreScreensBinding(true);
+        mWorkspace.mPageIndicator.setAreScreensBinding(true, mDeviceProfile.isTwoPanels);
         int firstScreenPosition = 0;
         if ((FeatureFlags.QSB_ON_FIRST_SCREEN
                 && mIsFirstPagePinnedItemEnabled
@@ -2650,7 +2638,7 @@
 
         TraceHelper.INSTANCE.endSection();
         mWorkspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true);
-        mWorkspace.mPageIndicator.setAreScreensBinding(false);
+        mWorkspace.mPageIndicator.setAreScreensBinding(false, mDeviceProfile.isTwoPanels);
     }
 
     private boolean canAnimatePageChange() {
@@ -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/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 4f8caab..aa06089 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -334,21 +334,21 @@
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
                 nonRestorableItem(
                         "pref_long_press_nav_handle_haptic_hint_end_scale_percent",
-                        50,
+                        100,
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
                 )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
                 nonRestorableItem(
                         "pref_long_press_nav_handle_haptic_hint_scale_exponent",
-                        2,
+                        1,
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
                 )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
                 nonRestorableItem(
                         "pref_long_press_nav_handle_haptic_hint_iterations",
-                        40,
+                        50,
                         EncryptionType.MOVE_TO_DEVICE_PROTECTED
                 )
         @JvmField
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 84d3805..2315111 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -49,6 +49,7 @@
 import android.animation.ValueAnimator;
 import android.util.FloatProperty;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.LauncherState.PageAlphaProvider;
@@ -164,6 +165,8 @@
                 state.hasFlag(FLAG_HOTSEAT_INACCESSIBLE)
                         ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                         : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+        hotseat.setDescendantFocusability(state.hasFlag(FLAG_HOTSEAT_INACCESSIBLE)
+                ? ViewGroup.FOCUS_BLOCK_DESCENDANTS : ViewGroup.FOCUS_BEFORE_DESCENDANTS);
 
         Interpolator translationInterpolator =
                 config.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 72c6cb8..7d52cbb 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));
@@ -560,7 +563,7 @@
             mainRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(0);
             workRecyclerView = (AllAppsRecyclerView) mViewPager.getChildAt(1);
             mAH.get(AdapterHolder.MAIN).setup(mainRecyclerView, mPersonalMatcher);
-            mAH.get(AdapterHolder.WORK).setup(workRecyclerView, mWorkManager.getMatcher());
+            mAH.get(AdapterHolder.WORK).setup(workRecyclerView, mWorkManager.getItemInfoMatcher());
             workRecyclerView.setId(R.id.apps_list_view_work);
             if (enableExpandingPauseWorkButton()
                     || FeatureFlags.ENABLE_EXPANDING_PAUSE_WORK_BUTTON.get()) {
@@ -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/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 005e6df..bce38a3 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -27,7 +27,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.config.FeatureFlags;
@@ -53,13 +52,16 @@
 
     public static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 4;
     public static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 5;
-
-    public static final int NEXT_ID = 6;
+    public static final int VIEW_TYPE_PRIVATE_SPACE_HEADER = 1 << 6;
+    public static final int NEXT_ID = 7;
 
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
 
+    public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER =
+            VIEW_TYPE_PRIVATE_SPACE_HEADER;
+
     protected final SearchAdapterProvider<?> mAdapterProvider;
 
     /**
@@ -197,6 +199,9 @@
             case VIEW_TYPE_WORK_DISABLED_CARD:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.work_apps_paused, parent, false));
+            case VIEW_TYPE_PRIVATE_SPACE_HEADER:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.private_space_header, parent, false));
             default:
                 if (mAdapterProvider.isViewSupported(viewType)) {
                     return mAdapterProvider.onCreateViewHolder(mLayoutInflater, parent, viewType);
@@ -213,6 +218,7 @@
                 BubbleTextView icon = (BubbleTextView) holder.itemView;
                 icon.reset();
                 icon.applyFromApplicationInfo(adapterItem.itemInfo);
+                icon.setOnFocusChangeListener(mIconFocusListener);
                 break;
             }
             case VIEW_TYPE_EMPTY_SEARCH: {
@@ -223,6 +229,7 @@
                 }
                 break;
             }
+            case VIEW_TYPE_PRIVATE_SPACE_HEADER:
             case VIEW_TYPE_ALL_APPS_DIVIDER:
             case VIEW_TYPE_WORK_DISABLED_CARD:
                 // nothing to do
diff --git a/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
new file mode 100644
index 0000000..f7c9058
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PrivateAppsSectionDecorator.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_ICON;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.view.View;
+
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.views.ActivityContext;
+
+/**
+ * Decorator which changes the background color for Private Space Icon Rows in AllAppsContainer.
+ */
+public class PrivateAppsSectionDecorator extends RecyclerView.ItemDecoration {
+
+    private final Path mTmpPath = new Path();
+    private final RectF mTmpRect = new RectF();
+    private final Context mContext;
+    private final AlphabeticalAppsList<?> mAppsList;
+    private final PrivateProfileManager mPrivateProfileManager;
+    private final UserCache mUserCache;
+    private final Paint mPaint;
+
+    public PrivateAppsSectionDecorator(ActivityAllAppsContainerView<?> appsContainerView,
+            AlphabeticalAppsList<?> appsList,
+            PrivateProfileManager privateProfileManager) {
+        mAppsList = appsList;
+        mPrivateProfileManager = privateProfileManager;
+        mContext = appsContainerView.mActivityContext;
+        mUserCache = UserCache.getInstance(appsContainerView.mActivityContext);
+        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaint.setColor(ContextCompat.getColor(mContext,
+                R.color.material_color_surface_container_high));
+    }
+
+    /** Decorates Private Space Header and Icon Rows to give the shape of a container. */
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        mTmpPath.reset();
+        mTmpRect.setEmpty();
+        int numCol = ActivityContext.lookupContext(mContext).getDeviceProfile()
+                .numShownAllAppsColumns;
+        for (int i = 0; i < parent.getChildCount(); i++) {
+            View view = parent.getChildAt(i);
+            int position = parent.getChildAdapterPosition(view);
+            BaseAllAppsAdapter.AdapterItem adapterItem = mAppsList.getAdapterItems().get(position);
+            // Rectangle that covers the bottom half of the PS Header View when Space is unlocked.
+            if (adapterItem.viewType == VIEW_TYPE_PRIVATE_SPACE_HEADER
+                    && mPrivateProfileManager
+                    .getCurrentState() == PrivateProfileManager.STATE_ENABLED) {
+                // We flatten the bottom corners of the rectangle, so that it merges with
+                // the private space app row decorator.
+                mTmpRect.set(
+                        view.getLeft(),
+                        view.getTop() + (float) (view.getBottom() - view.getTop()) / 2,
+                        view.getRight(),
+                        view.getBottom());
+                mTmpPath.addRect(mTmpRect, Path.Direction.CW);
+                c.drawPath(mTmpPath, mPaint);
+            } else if (adapterItem.viewType == VIEW_TYPE_ICON
+                    && mUserCache.getUserInfo(adapterItem.itemInfo.user).isPrivate()
+                    // No decoration for any private space app icon other than those at first row.
+                    && adapterItem.rowAppIndex == 0) {
+                c.drawPath(getPrivateAppRowPath(parent, view, position, numCol), mPaint);
+            }
+        }
+    }
+
+    /** Returns the path to be decorated for Private Space App Row */
+    private Path getPrivateAppRowPath(RecyclerView parent, View iconView, int adapterPosition,
+            int numCol) {
+        // We always decorate the entire app row here.
+        // As the iconView just represents the first icon of the row, we get the right margin of
+        // our decorator using the parent view.
+        mTmpRect.set(iconView.getLeft(),
+                iconView.getTop(),
+                parent.getRight() - parent.getPaddingRight(),
+                iconView.getBottom());
+        // Decorates last app row with rounded bottom corners.
+        if (adapterPosition + numCol >= mAppsList.getAdapterItems().size()) {
+            int corner = mContext.getResources().getDimensionPixelSize(
+                    R.dimen.ps_container_corner_radius);
+            float[] mCornersBot = new float[]{0, 0, 0, 0, corner, corner, corner, corner};
+            mTmpPath.addRoundRect(mTmpRect, mCornersBot, Path.Direction.CW);
+        } else {
+            // Decorate other rows as a plain rectangle
+            mTmpPath.addRect(mTmpRect, Path.Direction.CW);
+        }
+        return mTmpPath;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
new file mode 100644
index 0000000..ec01aee
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.function.Predicate;
+
+/**
+ * Companion class for {@link ActivityAllAppsContainerView} to manage private space section related
+ * logic in the Personal tab.
+ */
+public class PrivateProfileManager extends UserProfileManager {
+
+    private static final String SAFETY_CENTER_INTENT = Intent.ACTION_SAFETY_CENTER;
+    private static final String PS_SETTINGS_FRAGMENT_KEY = ":settings:fragment_args_key";
+    private static final String PS_SETTINGS_FRAGMENT_VALUE = "AndroidPrivateSpace_personal";
+    private final ActivityAllAppsContainerView<?> mAllApps;
+    private final Predicate<UserHandle> mPrivateProfileMatcher;
+
+    public PrivateProfileManager(UserManager userManager,
+            UserCache userCache,
+            ActivityAllAppsContainerView allApps,
+            StatsLogManager statsLogManager) {
+        super(userManager, statsLogManager, userCache);
+        mAllApps = allApps;
+        mPrivateProfileMatcher = (user) -> userCache.getUserInfo(user).isPrivate();
+    }
+
+    /** Adds Private Space Header to the layout. */
+    public int addPrivateSpaceHeader(ArrayList<BaseAllAppsAdapter.AdapterItem> adapterItems) {
+        adapterItems.add(new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER));
+        mAllApps.mAH.get(MAIN).mAdapter.notifyItemInserted(adapterItems.size() - 1);
+        return adapterItems.size();
+    }
+
+    /** Disables quiet mode for Private Space User Profile. */
+    public void unlockPrivateProfile() {
+        // TODO (b/302666597): Log this event to WW.
+        enableQuietMode(false);
+    }
+
+    /** Enables quiet mode for Private Space User Profile. */
+    public void lockPrivateProfile() {
+        // TODO (b/302666597): Log this event to WW.
+        enableQuietMode(true);
+    }
+
+    /** Whether private profile should be hidden on Launcher. */
+    public boolean isPrivateSpaceHidden() {
+        // TODO (b/289223923): Update this when we are able to read PsSettingsFlag
+        //  from SettingsProvider.
+        return false;
+    }
+
+    /** Resets the current state of Private Profile, w.r.t. to Launcher. */
+    public void reset() {
+        boolean isEnabled = !mAllApps.getAppsStore()
+                .hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED);
+        int updatedState = isEnabled ? STATE_ENABLED : STATE_DISABLED;
+        setCurrentState(updatedState);
+    }
+
+    /** Opens the Private Space Settings Entry Point. */
+    public void openPrivateSpaceSettings() {
+        // TODO (b/302666597): Log this event to WW.
+        Intent psSettingsIntent = new Intent(SAFETY_CENTER_INTENT);
+        psSettingsIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE);
+        mAllApps.getContext().startActivity(psSettingsIntent);
+    }
+
+    /**
+     * Whether Private Space Settings Entry Point should be made visible. */
+    public boolean isPrivateSpaceSettingsButtonVisible() {
+        Preconditions.assertNonUiThread();
+        Intent psSettingsIntent = new Intent(SAFETY_CENTER_INTENT);
+        psSettingsIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE);
+        ResolveInfo resolveInfo = mAllApps.getContext().getPackageManager()
+                .resolveActivity(psSettingsIntent, PackageManager.MATCH_SYSTEM_ONLY);
+        return resolveInfo != null;
+    }
+
+    /** Posts quiet mode enable/disable call for private profile. */
+    private void enableQuietMode(boolean enable) {
+        setQuietMode(enable);
+    }
+
+    @Override
+    public Predicate<UserHandle> getUserMatcher() {
+        return mPrivateProfileMatcher;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
new file mode 100644
index 0000000..9420b4c
--- /dev/null
+++ b/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewController.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static com.android.launcher3.allapps.PrivateProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.PrivateProfileManager.STATE_ENABLED;
+import static com.android.launcher3.allapps.PrivateProfileManager.STATE_TRANSITION;
+
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.UserProfileManager.UserProfileState;
+
+/**
+ * Controller which returns views to be added to Private Space Header based upon
+ * {@link UserProfileState}
+ */
+public class PrivateSpaceHeaderViewController {
+    private final PrivateProfileManager mPrivateProfileManager;
+
+    public PrivateSpaceHeaderViewController(PrivateProfileManager privateProfileManager) {
+        this.mPrivateProfileManager = privateProfileManager;
+    }
+
+    /** Add Private Space Header view elements based upon {@link UserProfileState} */
+    public void addPrivateSpaceHeaderViewElements(RelativeLayout parent) {
+        //Add quietMode image and action for lock/unlock button
+        ImageButton quietModeButton = parent.findViewById(R.id.ps_lock_unlock_button);
+        assert quietModeButton != null;
+        addQuietModeButton(quietModeButton);
+
+        //Add image and action for private space settings button
+        ImageButton settingsButton = parent.findViewById(R.id.ps_settings_button);
+        assert settingsButton != null;
+        addPrivateSpaceSettingsButton(settingsButton);
+
+        //Add image for private space transitioning view
+        ImageView transitionView = parent.findViewById(R.id.ps_transition_image);
+        assert transitionView != null;
+        addTransitionImage(transitionView);
+    }
+
+    private void addQuietModeButton(ImageButton quietModeButton) {
+        switch (mPrivateProfileManager.getCurrentState()) {
+            case STATE_ENABLED -> {
+                quietModeButton.setVisibility(View.VISIBLE);
+                quietModeButton.setImageResource(R.drawable.bg_ps_lock_button);
+                quietModeButton.setOnClickListener(
+                        view -> mPrivateProfileManager.lockPrivateProfile());
+            }
+            case STATE_DISABLED -> {
+                quietModeButton.setVisibility(View.VISIBLE);
+                quietModeButton.setImageResource(R.drawable.bg_ps_unlock_button);
+                quietModeButton.setOnClickListener(
+                        view -> mPrivateProfileManager.unlockPrivateProfile());
+            }
+            default -> quietModeButton.setVisibility(View.GONE);
+        }
+    }
+
+    private void addPrivateSpaceSettingsButton(ImageButton settingsButton) {
+        if (mPrivateProfileManager.getCurrentState() == STATE_ENABLED
+                && mPrivateProfileManager.isPrivateSpaceSettingsButtonVisible()) {
+            settingsButton.setVisibility(View.VISIBLE);
+            settingsButton.setOnClickListener(view ->
+                    mPrivateProfileManager.openPrivateSpaceSettings());
+        } else {
+            settingsButton.setVisibility(View.GONE);
+        }
+    }
+
+    private void addTransitionImage(ImageView transitionImage) {
+        if (mPrivateProfileManager.getCurrentState() == STATE_TRANSITION) {
+            transitionImage.setVisibility(View.VISIBLE);
+        } else {
+            transitionImage.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java
new file mode 100644
index 0000000..0261010
--- /dev/null
+++ b/src/com/android/launcher3/allapps/UserProfileManager.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Predicate;
+
+/**
+ * A Generic User Profile Manager which abstract outs the common functionality required
+ * by user-profiles supported by Launcher
+ * <p>
+ * Concrete impls are
+ * {@link WorkProfileManager} which manages work profile state
+ * {@link PrivateProfileManager} which manages private profile state.
+ */
+public abstract class UserProfileManager {
+    public static final int STATE_ENABLED = 1;
+    public static final int STATE_DISABLED = 2;
+    public static final int STATE_TRANSITION = 3;
+
+    @IntDef(value = {
+            STATE_ENABLED,
+            STATE_DISABLED,
+            STATE_TRANSITION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserProfileState { }
+
+    @UserProfileState
+    private int mCurrentState;
+
+    private final UserManager mUserManager;
+    private final StatsLogManager mStatsLogManager;
+    private final UserCache mUserCache;
+
+    protected UserProfileManager(UserManager userManager,
+            StatsLogManager statsLogManager,
+            UserCache userCache) {
+        mUserManager = userManager;
+        mStatsLogManager = statsLogManager;
+        mUserCache = userCache;
+    }
+
+    /** Sets quiet mode as enabled/disabled for the profile type. */
+    protected void setQuietMode(boolean enabled) {
+        if (Utilities.ATLEAST_P) {
+            UI_HELPER_EXECUTOR.post(() -> {
+                mUserCache.getUserProfiles()
+                        .stream()
+                        .filter(getUserMatcher())
+                        .findFirst()
+                        .ifPresent(userHandle ->
+                                mUserManager.requestQuietModeEnabled(enabled, userHandle));
+            });
+        }
+    }
+
+    /** Sets current state for the profile type. */
+    protected void setCurrentState(int state) {
+        mCurrentState = state;
+    }
+
+    /** Returns current state for the profile type. */
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    public int getCurrentState() {
+        return mCurrentState;
+    }
+
+    /** Logs Event to StatsLogManager. */
+    protected void logEvents(StatsLogManager.EventEnum event) {
+        mStatsLogManager.logger().log(event);
+    }
+
+    /** Returns the matcher corresponding to profile type. */
+    protected abstract Predicate<UserHandle> getUserMatcher();
+
+    /** Returns the matcher corresponding to the profile type associated with ItemInfo. */
+    protected Predicate<ItemInfo> getItemInfoMatcher() {
+        return itemInfo -> itemInfo != null && getUserMatcher().test(itemInfo.user);
+    }
+}
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index ac0e5a4..c430a36 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -25,31 +25,26 @@
 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.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
 
-import android.os.Build;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 import android.view.View;
 
-import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-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;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -57,59 +52,29 @@
 /**
  * Companion class for {@link ActivityAllAppsContainerView} to manage work tab and personal tab
  * related
- * logic based on {@link WorkProfileState}?
+ * logic based on {@link UserProfileState}?
  */
-public class WorkProfileManager implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
+public class WorkProfileManager extends UserProfileManager
+        implements PersonalWorkSlidingTabStrip.OnActivePageChangedListener {
     private static final String TAG = "WorkProfileManager";
-
-    public static final int STATE_ENABLED = 1;
-    public static final int STATE_DISABLED = 2;
-    public static final int STATE_TRANSITION = 3;
-
-    /**
-     * Work profile manager states
-     */
-    @IntDef(value = {
-            STATE_ENABLED,
-            STATE_DISABLED,
-            STATE_TRANSITION
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface WorkProfileState { }
-
-    private final UserManager mUserManager;
     private final ActivityAllAppsContainerView<?> mAllApps;
-    private final Predicate<ItemInfo> mMatcher;
-    private final StatsLogManager mStatsLogManager;
-
     private WorkModeSwitch mWorkModeSwitch;
-
-    @WorkProfileState
-    private int mCurrentState;
+    private final Predicate<UserHandle> mWorkProfileMatcher;
 
     public WorkProfileManager(
             UserManager userManager, ActivityAllAppsContainerView allApps,
-            StatsLogManager statsLogManager) {
-        mUserManager = userManager;
+            StatsLogManager statsLogManager, UserCache userCache) {
+        super(userManager, statsLogManager, userCache);
         mAllApps = allApps;
-        mMatcher = mAllApps.mPersonalMatcher.negate();
-        mStatsLogManager = statsLogManager;
+        mWorkProfileMatcher = (user) -> userCache.getUserInfo(user).isWork();
     }
 
     /**
      * Posts quite mode enable/disable call for work profile user
      */
-    @RequiresApi(Build.VERSION_CODES.P)
     public void setWorkProfileEnabled(boolean enabled) {
-        updateCurrentState(STATE_TRANSITION);
-        UI_HELPER_EXECUTOR.post(() -> {
-            for (UserHandle userProfile : mUserManager.getUserProfiles()) {
-                if (Process.myUserHandle().equals(userProfile)) {
-                    continue;
-                }
-                mUserManager.requestQuietModeEnabled(!enabled, userProfile);
-            }
-        });
+        setCurrentState(STATE_TRANSITION);
+        setQuietMode(!enabled);
     }
 
     @Override
@@ -121,7 +86,7 @@
         if (mWorkModeSwitch != null) {
             if (page == MAIN || page == SEARCH) {
                 mWorkModeSwitch.animateVisibility(false);
-            } else if (page == WORK && mCurrentState == STATE_ENABLED) {
+            } else if (page == WORK && getCurrentState() == STATE_ENABLED) {
                 mWorkModeSwitch.animateVisibility(true);
             }
         }
@@ -131,7 +96,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.
@@ -140,17 +111,17 @@
         }
     }
 
-    private void updateCurrentState(@WorkProfileState int currentState) {
-        mCurrentState = currentState;
+    private void updateCurrentState(@UserProfileState int currentState) {
+        setCurrentState(currentState);
         if (getAH() != null) {
             getAH().mAppsList.updateAdapterItems();
         }
         if (mWorkModeSwitch != null) {
             updateWorkFAB(mAllApps.getCurrentPage());
         }
-        if (mCurrentState == STATE_ENABLED) {
+        if (getCurrentState() == STATE_ENABLED) {
             attachWorkModeSwitch();
-        } else if (mCurrentState == STATE_DISABLED) {
+        } else if (getCurrentState() == STATE_DISABLED) {
             detachWorkModeSwitch();
         }
     }
@@ -190,10 +161,6 @@
         mWorkModeSwitch = null;
     }
 
-    public Predicate<ItemInfo> getMatcher() {
-        return mMatcher;
-    }
-
     @Nullable
     public WorkModeSwitch getWorkModeSwitch() {
         return mWorkModeSwitch;
@@ -203,29 +170,25 @@
         return mAllApps.mAH.get(WORK);
     }
 
-    public int getCurrentState() {
-        return mCurrentState;
-    }
-
     /**
      * returns whether or not work apps should be visible in work tab.
      */
     public boolean shouldShowWorkApps() {
-        return mCurrentState != WorkProfileManager.STATE_DISABLED;
+        return getCurrentState() != WorkProfileManager.STATE_DISABLED;
     }
 
     public boolean hasWorkApps() {
-        return Stream.of(mAllApps.getAppsStore().getApps()).anyMatch(mMatcher);
+        return Stream.of(mAllApps.getAppsStore().getApps()).anyMatch(getItemInfoMatcher());
     }
 
     /**
      * Adds work profile specific adapter items to adapterItems and returns number of items added
      */
     public int addWorkItems(ArrayList<AdapterItem> adapterItems) {
-        if (mCurrentState == WorkProfileManager.STATE_DISABLED) {
+        if (getCurrentState() == WorkProfileManager.STATE_DISABLED) {
             //add disabled card here.
             adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_DISABLED_CARD));
-        } else if (mCurrentState == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
+        } else if (getCurrentState() == WorkProfileManager.STATE_ENABLED && !isEduSeen()) {
             adapterItems.add(new AdapterItem(VIEW_TYPE_WORK_EDU_CARD));
         }
         return adapterItems.size();
@@ -236,8 +199,9 @@
     }
 
     private void onWorkFabClicked(View view) {
-        if (Utilities.ATLEAST_P && mCurrentState == STATE_ENABLED && mWorkModeSwitch.isEnabled()) {
-            mStatsLogManager.logger().log(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
+        if (Utilities.ATLEAST_P && getCurrentState() == STATE_ENABLED
+                && mWorkModeSwitch.isEnabled()) {
+            logEvents(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
             setWorkProfileEnabled(false);
         }
     }
@@ -268,4 +232,9 @@
             }
         };
     }
+
+    @Override
+    public Predicate<UserHandle> getUserMatcher() {
+        return mWorkProfileMatcher;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index ecbc7a9..4427a49 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -160,7 +160,7 @@
      * Focuses the search field to handle key events.
      */
     public void focusSearchField() {
-        mInput.showKeyboard(true /* shouldFocus */);
+        mInput.showKeyboard();
     }
 
     /**
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4e7ec0b..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;
 
@@ -119,6 +120,10 @@
             getReleaseFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
                     "Add dev options to customize the LPNH trigger slop and milliseconds");
 
+    public static final BooleanFlag ANIMATE_LPNH =
+            getReleaseFlag(308693847, "ANIMATE_LPNH", TEAMFOOD,
+                    "Animates navbar when long pressing");
+
     public static final IntFlag LPNH_SLOP_PERCENTAGE =
             getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
                     "Controls touch slop percentage for lpnh");
@@ -213,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
@@ -271,15 +283,20 @@
                     "Enables long pressing on the bottom bar nav handle to trigger events.");
 
     public static final BooleanFlag ENABLE_SEARCH_HAPTIC_HINT =
-            getReleaseFlag(303023676, "ENABLE_SEARCH_HAPTIC_HINT", TEAMFOOD,
+            getReleaseFlag(303023676, "ENABLE_SEARCH_HAPTIC_HINT", ENABLED,
                     "Enables haptic hint when long pressing on the bottom bar nav handle.");
 
     // TODO(Block 17): Clean up flags
-    public static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
-            "ENABLE_TASKBAR_PINNING", DISABLED,
+    // 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"
@@ -391,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/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 57e1641..8bf7ec2 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -536,7 +536,7 @@
                     mFolderName.selectAll();
                 }
             }
-            mFolderName.showKeyboard(true /* shouldFocus */);
+            mFolderName.showKeyboard();
             mFolderName.displayCompletions(
                     Stream.of(mInfo.suggestedFolderNames.getLabels())
                             .filter(Objects::nonNull)
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/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index 18200f6..69fa673 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -169,6 +169,9 @@
                 LauncherPrefs.get(getContext())
                         .put(THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE));
                 getContext().getContentResolver().notifyChange(uri, null);
+                mActivePreviews.values().forEach(observer ->
+                        observer.renderer.refreshIcons()
+                );
                 return 1;
             }
             default:
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 3330448..346f644 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -35,9 +35,12 @@
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
 import android.content.res.TypedArray;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -76,6 +79,7 @@
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -83,6 +87,7 @@
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
@@ -188,6 +193,8 @@
     private final SparseIntArray mWallpaperColorResources;
     private final SparseArray<Size> mLauncherWidgetSpanInfo;
 
+    private final Map<BubbleTextView, WorkspaceItemInfo> mIcons = new HashMap<>();
+
     public LauncherPreviewRenderer(Context context,
             InvariantDeviceProfile idp,
             WallpaperColors wallpaperColorsOverride,
@@ -366,11 +373,43 @@
         return CellPosMapper.DEFAULT;
     }
 
+    /**
+     * Refreshes icon to update based on resource changes.
+     */
+    public void refreshIcons() {
+        mUiHandler.post(() -> {
+                    IconProvider iconProvider = null;
+                    int iconDpi = -1;
+                    for (Map.Entry<BubbleTextView, WorkspaceItemInfo> entry : mIcons.entrySet()) {
+                        BubbleTextView icon = entry.getKey();
+                        ItemInfoWithIcon info = entry.getValue();
+                        // get monochrome themed icon if it was not initially cached
+                        if (info.bitmap.getMono() == null) {
+                            if (iconProvider == null || iconDpi == -1) {
+                                LauncherAppState appState = LauncherAppState.getInstance(mContext);
+                                iconProvider = appState.getIconProvider();
+                                iconDpi = appState.getInvariantDeviceProfile().fillResIconDpi;
+                            }
+                            LauncherActivityInfo activityInfo = mContext.getSystemService(
+                                            LauncherApps.class)
+                                    .resolveActivity(info.getIntent(), info.user);
+                            Drawable iconDrawable = iconProvider.getIcon(activityInfo,  iconDpi);
+                            LauncherIcons iconFactory = LauncherIcons.obtain(mContext);
+                            info.bitmap = iconFactory.createBadgedIconBitmap(iconDrawable);
+                        }
+                        // update icon based on whether themed icon is enabled
+                        icon.reapplyItemInfo(info);
+                    }
+                }
+        );
+    }
+
     private void inflateAndAddIcon(WorkspaceItemInfo info) {
         CellLayout screen = mWorkspaceScreens.get(info.screenId);
         BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
                 R.layout.app_icon, screen, false);
         icon.applyFromWorkspaceItem(info);
+        mIcons.put(icon, info);
         addInScreenFromBind(icon, info);
     }
 
@@ -477,6 +516,8 @@
                 currentWorkspaceItems, otherWorkspaceItems);
         filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
                 otherAppWidgets);
+
+        mIcons.clear();
         for (ItemInfo itemInfo : currentWorkspaceItems) {
             switch (itemInfo.itemType) {
                 case Favorites.ITEM_TYPE_APPLICATION:
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 683354b..1bac765 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -201,6 +201,13 @@
         }
     }
 
+    /**
+     * Refreshes icon to update based on resource changes.
+     */
+    public void refreshIcons() {
+        mRenderer.refreshIcons();
+    }
+
     /***
      * Generates a new context overriding the theme color and the display size without affecting the
      * main application context
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 8f85bfb..190eb78 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -79,6 +79,8 @@
      * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
      * @see Callbacks#FLAG_QUIET_MODE_ENABLED
      * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
+     * @see Callbacks#FLAG_WORK_PROFILE_QUIET_MODE_ENABLED
+     * @see Callbacks#FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED
      */
     private int mFlags;
 
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 54ecc00..7f0f683 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -477,6 +477,10 @@
         int FLAG_QUIET_MODE_ENABLED = 1 << 1;
         // If launcher can change quiet mode
         int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
+        // If quiet mode is enabled for work profile user
+        int FLAG_WORK_PROFILE_QUIET_MODE_ENABLED = 1 << 3;
+        // If quiet mode is enabled for private profile user
+        int FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED = 1 << 4;
 
         /**
          * Returns an IntSet of page ids to bind first, synchronously if possible
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 1ab0355..f4ce360 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -23,8 +23,10 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_SMARTSPACE_REMOVAL;
 import static com.android.launcher3.config.FeatureFlags.SMARTSPACE_AS_A_WIDGET;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
@@ -57,8 +59,10 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
@@ -143,7 +147,7 @@
     private final InstallSessionHelper mSessionHelper;
     private final IconCache mIconCache;
 
-    private final UserManagerState mUserManagerState = new UserManagerState();
+    private final UserManagerState mUserManagerState;
 
     protected final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap = new ArrayMap<>();
     private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts;
@@ -156,6 +160,13 @@
 
     public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
             ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder) {
+        this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, new UserManagerState());
+    }
+
+    @VisibleForTesting
+    LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
+            ModelDelegate modelDelegate, @NonNull LauncherBinder launcherBinder,
+            UserManagerState userManagerState) {
         mApp = app;
         mBgAllAppsList = bgAllAppsList;
         mBgDataModel = bgModel;
@@ -164,9 +175,10 @@
 
         mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
         mUserManager = mApp.getContext().getSystemService(UserManager.class);
-        mUserCache = UserCache.INSTANCE.get(mApp.getContext());
+        mUserCache = UserCache.getInstance(mApp.getContext());
         mSessionHelper = InstallSessionHelper.INSTANCE.get(mApp.getContext());
         mIconCache = mApp.getIconCache();
+        mUserManagerState = userManagerState;
     }
 
     protected synchronized void waitForIdle() {
@@ -973,6 +985,8 @@
         mBgAllAppsList.clear();
 
         List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
+        boolean isWorkProfileQuiet = false;
+        boolean isPrivateProfileQuiet = false;
         for (UserHandle user : profiles) {
             // Query for the set of apps
             final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
@@ -982,6 +996,14 @@
                 return allActivityList;
             }
             boolean quietMode = mUserManagerState.isUserQuiet(user);
+
+            if (Flags.enablePrivateSpace()) {
+                if (mUserCache.getUserInfo(user).isWork()) {
+                    isWorkProfileQuiet = quietMode;
+                } else if (mUserCache.getUserInfo(user).isPrivate()) {
+                    isPrivateProfileQuiet = quietMode;
+                }
+            }
             // Create the ApplicationInfos
             for (int i = 0; i < apps.size(); i++) {
                 LauncherActivityInfo app = apps.get(i);
@@ -1023,8 +1045,13 @@
             Trace.endSection();
         }
 
-        mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
-                mUserManagerState.isAnyProfileQuietModeEnabled());
+        if (Flags.enablePrivateSpace()) {
+            mBgAllAppsList.setFlags(FLAG_WORK_PROFILE_QUIET_MODE_ENABLED, isWorkProfileQuiet);
+            mBgAllAppsList.setFlags(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED, isPrivateProfileQuiet);
+        } else {
+            mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
+                    mUserManagerState.isAnyProfileQuietModeEnabled());
+        }
         mBgAllAppsList.setFlags(FLAG_HAS_SHORTCUT_PERMISSION,
                 hasShortcutsPermission(mApp.getContext()));
         mBgAllAppsList.setFlags(FLAG_QUIET_MODE_CHANGE_PERMISSION,
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 37a7171..9a0a6eb 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,7 +15,9 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
 
@@ -31,6 +33,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
@@ -169,14 +172,24 @@
                 break;
             case OP_USER_AVAILABILITY_CHANGE: {
                 UserManagerState ums = new UserManagerState();
-                ums.init(UserCache.INSTANCE.get(context),
-                        context.getSystemService(UserManager.class));
+                UserManager userManager = context.getSystemService(UserManager.class);
+                ums.init(UserCache.INSTANCE.get(context), userManager);
+                boolean isUserQuiet =  ums.isUserQuiet(mUser);
                 flagOp = FlagOp.NO_OP.setFlag(
-                        WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER, ums.isUserQuiet(mUser));
+                        WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER, isUserQuiet);
                 appsList.updateDisabledFlags(matcher, flagOp);
 
-                // We are not synchronizing here, as int operations are atomic
-                appsList.setFlags(FLAG_QUIET_MODE_ENABLED, ums.isAnyProfileQuietModeEnabled());
+                if (Flags.enablePrivateSpace()) {
+                    UserCache userCache = UserCache.INSTANCE.get(context);
+                    if (userCache.getUserInfo(mUser).isWork()) {
+                        appsList.setFlags(FLAG_WORK_PROFILE_QUIET_MODE_ENABLED, isUserQuiet);
+                    } else if (userCache.getUserInfo(mUser).isPrivate()) {
+                        appsList.setFlags(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED, isUserQuiet);
+                    }
+                } else {
+                    // We are not synchronizing here, as int operations are atomic
+                    appsList.setFlags(FLAG_QUIET_MODE_ENABLED, ums.isAnyProfileQuietModeEnabled());
+                }
                 break;
             }
             default:
diff --git a/src/com/android/launcher3/model/UserManagerState.java b/src/com/android/launcher3/model/UserManagerState.java
index 97a5905..720f08e 100644
--- a/src/com/android/launcher3/model/UserManagerState.java
+++ b/src/com/android/launcher3/model/UserManagerState.java
@@ -61,6 +61,9 @@
 
     /**
      * Returns true if any user profile has quiet mode enabled.
+     * <p>
+     * Do not use this for determining if a specific profile has quiet mode enabled, as their can
+     * be more than one profile in quiet mode.
      */
     public boolean isAnyProfileQuietModeEnabled() {
         for (int i = mQuietUsersHashCodeMap.size() - 1; i >= 0; i--) {
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
index 4ab2037..30156c8 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -30,7 +30,7 @@
      * Sets flag to indicate when the screens are in the process of binding so that we don't animate
      * during that period.
      */
-    default void setAreScreensBinding(boolean areScreensBinding) {
+    default void setAreScreensBinding(boolean areScreensBinding, boolean isTwoPanels) {
         // No-op by default
     }
 
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 323b3a7..fd1b64f 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.pageindicators;
 
+import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -129,6 +131,7 @@
     private float mCurrentPosition;
     private float mFinalPosition;
     private boolean mAreScreensBinding;
+    private boolean mIsTwoPanels;
     private ObjectAnimator mAnimator;
     private @Nullable ObjectAnimator mAlphaAnimator;
 
@@ -348,6 +351,12 @@
 
     @Override
     public void setActiveMarker(int activePage) {
+        // In unfolded foldables, every page has two CellLayouts, so we need to halve the active
+        // page for it to be accurate.
+        if (mIsTwoPanels && !FOLDABLE_SINGLE_PAGE.get()) {
+            activePage = activePage / 2;
+        }
+
         if (mActivePage != activePage) {
             mActivePage = activePage;
         }
@@ -360,7 +369,9 @@
     }
 
     @Override
-    public void setAreScreensBinding(boolean areScreensBinding) {
+    public void setAreScreensBinding(boolean areScreensBinding, boolean isTwoPanels) {
+        mIsTwoPanels = isTwoPanels;
+
         // Reapply correct current position which was skipped during setScroll.
         if (mAreScreensBinding && !areScreensBinding) {
             CURRENT_POSITION.set(this, (float) mActivePage);
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index e2b1286..4661fd4 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -55,10 +55,18 @@
             ? Intent.ACTION_PROFILE_ACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNLOCKED;
     public static final String ACTION_PROFILE_LOCKED = ATLEAST_U
             ? Intent.ACTION_PROFILE_INACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
+    public static final String ACTION_PROFILE_AVAILABLE = "android.intent.action.PROFILE_AVAILABLE";
+    public static final String ACTION_PROFILE_UNAVAILABLE =
+            "android.intent.action.PROFILE_UNAVAILABLE";
 
     public static final MainThreadInitializedObject<UserCache> INSTANCE =
             new MainThreadInitializedObject<>(UserCache::new);
 
+    /** Returns an instance of UserCache bound to the context provided. */
+    public static UserCache getInstance(Context context) {
+        return INSTANCE.get(context);
+    }
+
     private final List<BiConsumer<UserHandle, String>> mUserEventListeners = new ArrayList<>();
     private final SimpleBroadcastReceiver mUserChangeReceiver =
             new SimpleBroadcastReceiver(this::onUsersChanged);
@@ -87,7 +95,9 @@
                 ACTION_PROFILE_ADDED,
                 ACTION_PROFILE_REMOVED,
                 ACTION_PROFILE_UNLOCKED,
-                ACTION_PROFILE_LOCKED);
+                ACTION_PROFILE_LOCKED,
+                ACTION_PROFILE_AVAILABLE,
+                ACTION_PROFILE_UNAVAILABLE);
         updateCache();
     }
 
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 6b08153..e3314d4 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -127,6 +127,7 @@
 
     protected final float mElevation;
 
+    // Tag for Views that have children that will need to be iterated to add styling.
     private final String mIterateChildrenTag;
 
     protected final int[] mColorIds;
@@ -244,7 +245,7 @@
         for (int i = 0; i < count; i++) {
             View view = viewGroup.getChildAt(i);
             if (view.getVisibility() == VISIBLE) {
-                if (lastView != null) {
+                if (lastView != null && (isShortcutContainer(lastView))) {
                     MarginLayoutParams mlp = (MarginLayoutParams) lastView.getLayoutParams();
                     mlp.bottomMargin = mChildContainerMargin;
                 }
@@ -415,8 +416,7 @@
     private void orientAboutObject(boolean allowAlignLeft, boolean allowAlignRight) {
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
 
-        int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical
-                + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+        int extraVerticalSpace = mArrowHeight + mArrowOffsetVertical + getExtraVerticalOffset();
         // The margins are added after we call this method, so we need to account for them here.
         int numVisibleChildren = 0;
         for (int i = getChildCount() - 1; i >= 0; --i) {
@@ -632,6 +632,10 @@
         mOpenCloseAnimator.start();
     }
 
+    public int getExtraVerticalOffset() {
+        return getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+    }
+
     protected AnimatorSet getOpenCloseAnimator(boolean isOpening, int scaleDuration,
             int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration,
             Interpolator interpolator) {
diff --git a/src/com/android/launcher3/responsive/AllAppsSpecs.kt b/src/com/android/launcher3/responsive/AllAppsSpecs.kt
deleted file mode 100644
index 8ed3ffc..0000000
--- a/src/com/android/launcher3/responsive/AllAppsSpecs.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.responsive
-
-import android.content.res.TypedArray
-import com.android.launcher3.R
-import com.android.launcher3.responsive.ResponsiveSpec.SpecType
-import com.android.launcher3.util.ResourceHelper
-
-class AllAppsSpecs(widthSpecs: List<AllAppsSpec>, heightSpecs: List<AllAppsSpec>) :
-    ResponsiveSpecs<AllAppsSpec>(widthSpecs, heightSpecs) {
-
-    fun getCalculatedWidthSpec(
-        columns: Int,
-        availableWidth: Int,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-    ): CalculatedAllAppsSpec {
-        check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
-            "Invalid specType for CalculatedWorkspaceSpec. " +
-                "Expected: ${SpecType.WIDTH} - " +
-                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
-        }
-
-        val spec = getWidthSpec(availableWidth)
-        return CalculatedAllAppsSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
-    }
-
-    fun getCalculatedHeightSpec(
-        rows: Int,
-        availableHeight: Int,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-    ): CalculatedAllAppsSpec {
-        check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
-            "Invalid specType for CalculatedWorkspaceSpec. " +
-                "Expected: ${SpecType.HEIGHT} - " +
-                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
-        }
-
-        val spec = getHeightSpec(availableHeight)
-        return CalculatedAllAppsSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
-    }
-
-    companion object {
-        private const val XML_ALL_APPS_SPEC = "allAppsSpec"
-
-        @JvmStatic
-        fun create(resourceHelper: ResourceHelper): AllAppsSpecs {
-            val parser = ResponsiveSpecsParser(resourceHelper)
-            val specs = parser.parseXML(XML_ALL_APPS_SPEC, ::AllAppsSpec)
-            val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
-            return AllAppsSpecs(widthSpecs, heightSpecs)
-        }
-    }
-}
-
-data class AllAppsSpec(
-    override val maxAvailableSize: Int,
-    override val specType: SpecType,
-    override val startPadding: SizeSpec,
-    override val endPadding: SizeSpec,
-    override val gutter: SizeSpec,
-    override val cellSize: SizeSpec
-) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
-
-    init {
-        check(isValid()) { "Invalid AllAppsSpec found." }
-    }
-
-    constructor(
-        attrs: TypedArray,
-        specs: Map<String, SizeSpec>
-    ) : this(
-        maxAvailableSize =
-            attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
-        specType =
-            SpecType.values()[
-                    attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
-        startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
-        endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
-        gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
-        cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
-    )
-}
-
-class CalculatedAllAppsSpec(
-    availableSpace: Int,
-    cells: Int,
-    spec: AllAppsSpec,
-    calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
diff --git a/src/com/android/launcher3/responsive/FolderSpecs.kt b/src/com/android/launcher3/responsive/FolderSpecs.kt
deleted file mode 100644
index bc2db28..0000000
--- a/src/com/android/launcher3/responsive/FolderSpecs.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.responsive
-
-import android.content.res.TypedArray
-import com.android.launcher3.R
-import com.android.launcher3.responsive.ResponsiveSpec.SpecType
-import com.android.launcher3.util.ResourceHelper
-
-class FolderSpecs(widthSpecs: List<FolderSpec>, heightSpecs: List<FolderSpec>) :
-    ResponsiveSpecs<FolderSpec>(widthSpecs, heightSpecs) {
-
-    fun getCalculatedWidthSpec(
-        columns: Int,
-        availableWidth: Int,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-    ): CalculatedFolderSpec {
-        check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
-            "Invalid specType for CalculatedWorkspaceSpec. " +
-                "Expected: ${SpecType.WIDTH} - " +
-                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
-        }
-
-        val spec = getWidthSpec(availableWidth)
-        return CalculatedFolderSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
-    }
-
-    fun getCalculatedHeightSpec(
-        rows: Int,
-        availableHeight: Int,
-        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-    ): CalculatedFolderSpec {
-        check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
-            "Invalid specType for CalculatedWorkspaceSpec. " +
-                "Expected: ${SpecType.HEIGHT} - " +
-                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
-        }
-
-        val spec = getHeightSpec(availableHeight)
-        return CalculatedFolderSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
-    }
-
-    companion object {
-
-        private const val XML_FOLDER_SPEC = "folderSpec"
-
-        @JvmStatic
-        fun create(resourceHelper: ResourceHelper): FolderSpecs {
-            val parser = ResponsiveSpecsParser(resourceHelper)
-            val specs = parser.parseXML(XML_FOLDER_SPEC, ::FolderSpec)
-            val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
-            return FolderSpecs(widthSpecs, heightSpecs)
-        }
-    }
-}
-
-data class FolderSpec(
-    override val maxAvailableSize: Int,
-    override val specType: SpecType,
-    override val startPadding: SizeSpec,
-    override val endPadding: SizeSpec,
-    override val gutter: SizeSpec,
-    override val cellSize: SizeSpec
-) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
-
-    init {
-        check(isValid()) { "Invalid FolderSpec found." }
-    }
-
-    constructor(
-        attrs: TypedArray,
-        specs: Map<String, SizeSpec>
-    ) : this(
-        maxAvailableSize =
-            attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
-        specType =
-            SpecType.values()[
-                    attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
-        startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
-        endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
-        gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
-        cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
-    )
-}
-
-class CalculatedFolderSpec(
-    availableSpace: Int,
-    cells: Int,
-    spec: FolderSpec,
-    calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
diff --git a/src/com/android/launcher3/responsive/HotseatSpecs.kt b/src/com/android/launcher3/responsive/HotseatSpecsProvider.kt
similarity index 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/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 228af4c..650fbcc 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -18,7 +18,6 @@
 
 import static android.provider.Settings.Global.DEVELOPMENT_SETTINGS_ENABLED;
 
-import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
 import static androidx.preference.PreferenceFragmentCompat.ARG_PREFERENCE_ROOT;
 
 import static com.android.launcher3.BuildConfig.IS_DEBUG_DEVICE;
@@ -34,7 +33,6 @@
 import android.view.MenuItem;
 import android.view.View;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.view.WindowCompat;
@@ -290,8 +288,6 @@
                 if (highlighter != null) {
                     getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS);
                     mPreferenceHighlighted = true;
-                } else {
-                    requestAccessibilityFocus(getListView());
                 }
             }
 
@@ -350,14 +346,5 @@
                     list, position, screen.findPreference(mHighLightKey))
                     : null;
         }
-
-        private void requestAccessibilityFocus(@NonNull final RecyclerView rv) {
-            rv.post(() -> {
-                if (!rv.hasFocus() && rv.getChildCount() > 0) {
-                    rv.getChildAt(0)
-                            .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
-                }
-            });
-        }
     }
 }
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index 70691f8..60d0e95 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -76,7 +76,7 @@
             // be more useful.
             // That's why we pass false as the value for the 'reportToTapl' parameter.
             recordEventSlow(sequence, message + ": " + event, false);
-            registerEventNotFromTest(event);
+            if (action != MotionEvent.ACTION_CANCEL) registerEventNotFromTest(event);
         }
     }
 
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 8c12547..db32829 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -15,8 +15,8 @@
  */
 package com.android.launcher3.touch;
 
-import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET;
-import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
diff --git a/src/com/android/launcher3/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/PrivateProfileManagerTest.java b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
new file mode 100644
index 0000000..bfa9241
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
+import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.UserIconInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class PrivateProfileManagerTest {
+
+    private static final UserHandle MAIN_HANDLE = Process.myUserHandle();
+    private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
+    private static final UserIconInfo MAIN_ICON_INFO =
+            new UserIconInfo(MAIN_HANDLE, UserIconInfo.TYPE_MAIN);
+    private static final UserIconInfo PRIVATE_ICON_INFO =
+            new UserIconInfo(PRIVATE_HANDLE, UserIconInfo.TYPE_PRIVATE);
+    private static final String SAFETY_CENTER_INTENT = Intent.ACTION_SAFETY_CENTER;
+    private static final String PS_SETTINGS_FRAGMENT_KEY = ":settings:fragment_args_key";
+    private static final String PS_SETTINGS_FRAGMENT_VALUE = "AndroidPrivateSpace_personal";
+
+    private PrivateProfileManager mPrivateProfileManager;
+    @Mock
+    private ActivityAllAppsContainerView mActivityAllAppsContainerView;
+    @Mock
+    private StatsLogManager mStatsLogManager;
+    @Mock
+    private UserCache mUserCache;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private Context mContext;
+    @Mock
+    private AllAppsStore mAllAppsStore;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mUserCache.getUserProfiles())
+                .thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE));
+        when(mUserCache.getUserInfo(Process.myUserHandle())).thenReturn(MAIN_ICON_INFO);
+        when(mUserCache.getUserInfo(PRIVATE_HANDLE)).thenReturn(PRIVATE_ICON_INFO);
+        when(mActivityAllAppsContainerView.getContext()).thenReturn(mContext);
+        when(mActivityAllAppsContainerView.getAppsStore()).thenReturn(mAllAppsStore);
+        mPrivateProfileManager = new PrivateProfileManager(mUserManager, mUserCache,
+                mActivityAllAppsContainerView, mStatsLogManager);
+    }
+
+    @Test
+    public void lockPrivateProfile_requestsQuietModeAsTrue() throws Exception {
+        when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(false);
+
+        mPrivateProfileManager.lockPrivateProfile();
+
+        awaitTasksCompleted();
+        Mockito.verify(mUserManager).requestQuietModeEnabled(true, PRIVATE_HANDLE);
+    }
+
+    @Test
+    public void unlockPrivateProfile_requestsQuietModeAsFalse() throws Exception {
+        when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED)).thenReturn(true);
+
+        mPrivateProfileManager.unlockPrivateProfile();
+
+        awaitTasksCompleted();
+        Mockito.verify(mUserManager).requestQuietModeEnabled(false, PRIVATE_HANDLE);
+    }
+
+    @Test
+    public void quietModeFlagPresent_privateSpaceIsResetToDisabled() {
+        when(mAllAppsStore.hasModelFlag(FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED))
+                .thenReturn(false, true);
+
+        // In first call the state should be disabled.
+        mPrivateProfileManager.reset();
+        assertEquals(STATE_ENABLED, mPrivateProfileManager.getCurrentState());
+
+        // In the next call the state should be disabled.
+        mPrivateProfileManager.reset();
+        assertEquals(STATE_DISABLED, mPrivateProfileManager.getCurrentState());
+    }
+
+    @Test
+    public void openPrivateSpaceSettings_triggersSecurityAndPrivacyIntent() {
+        Intent expectedIntent = new Intent(SAFETY_CENTER_INTENT);
+        expectedIntent.putExtra(PS_SETTINGS_FRAGMENT_KEY, PS_SETTINGS_FRAGMENT_VALUE);
+        ArgumentCaptor<Intent> acIntent = ArgumentCaptor.forClass(Intent.class);
+
+        mPrivateProfileManager.openPrivateSpaceSettings();
+
+        Mockito.verify(mContext).startActivity(acIntent.capture());
+        Intent actualIntent = acIntent.getValue();
+        assertEquals(expectedIntent.getAction(), actualIntent.getAction());
+        assertEquals(expectedIntent.getStringExtra(PS_SETTINGS_FRAGMENT_KEY),
+                actualIntent.getStringExtra(PS_SETTINGS_FRAGMENT_KEY));
+    }
+
+    private static void awaitTasksCompleted() throws Exception {
+        UI_HELPER_EXECUTOR.submit(() -> null).get();
+    }
+}
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
new file mode 100644
index 0000000..87adaa1
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewControllerTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.allapps;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
+import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.ActivityContextWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PrivateSpaceHeaderViewControllerTest {
+
+    private static final int CONTAINER_HEADER_ELEMENT_COUNT = 1;
+    private static final int LOCK_UNLOCK_BUTTON_COUNT = 1;
+    private static final int PS_SETTINGS_BUTTON_COUNT_VISIBLE = 1;
+    private static final int PS_SETTINGS_BUTTON_COUNT_INVISIBLE = 0;
+    private static final int PS_TRANSITION_IMAGE_COUNT = 1;
+
+    private Context mContext;
+    private LayoutInflater mLayoutInflater;
+    private PrivateSpaceHeaderViewController mPsHeaderViewController;
+    private RelativeLayout mPsHeaderLayout;
+    @Mock
+    private PrivateProfileManager mPrivateProfileManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = new ActivityContextWrapper(getApplicationContext());
+        mLayoutInflater = LayoutInflater.from(getApplicationContext());
+        mPsHeaderViewController = new PrivateSpaceHeaderViewController(mPrivateProfileManager);
+        mPsHeaderLayout = (RelativeLayout) mLayoutInflater.inflate(R.layout.private_space_header,
+                null);
+    }
+
+    @Test
+    public void privateProfileDisabled_psHeaderContainsLockedView() {
+        Bitmap unlockButton = getBitmap(mContext.getDrawable(R.drawable.bg_ps_unlock_button));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_DISABLED);
+
+        mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+
+        int totalContainerHeaderView = 0;
+        int totalLockUnlockButtonView = 0;
+        for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+            View view = mPsHeaderLayout.getChildAt(i);
+            if (view.getId() == R.id.ps_container_header) {
+                totalContainerHeaderView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+            } else if (view.getId() == R.id.ps_lock_unlock_button
+                    && view instanceof ImageView imageView) {
+                totalLockUnlockButtonView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(unlockButton);
+            } else {
+                assertEquals(View.GONE, view.getVisibility());
+            }
+        }
+        assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+        assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+    }
+
+    @Test
+    public void privateProfileEnabled_psHeaderContainsUnlockedView() {
+        Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_lock_button));
+        Bitmap settingsImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_settings_button));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+        when(mPrivateProfileManager.isPrivateSpaceSettingsButtonVisible()).thenReturn(true);
+
+        mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+
+        int totalContainerHeaderView = 0;
+        int totalLockUnlockButtonView = 0;
+        int totalSettingsImageView = 0;
+        for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+            View view = mPsHeaderLayout.getChildAt(i);
+            if (view.getId() == R.id.ps_container_header) {
+                totalContainerHeaderView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+            } else if (view.getId() == R.id.ps_lock_unlock_button
+                    && view instanceof ImageView imageView) {
+                totalLockUnlockButtonView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(lockImage);
+            } else if (view.getId() == R.id.ps_settings_button
+                    && view instanceof ImageView imageView) {
+                totalSettingsImageView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(settingsImage);
+            } else {
+                assertEquals(View.GONE, view.getVisibility());
+            }
+        }
+        assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+        assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+        assertEquals(PS_SETTINGS_BUTTON_COUNT_VISIBLE, totalSettingsImageView);
+    }
+
+    @Test
+    public void privateProfileEnabledAndNoSettingsIntent_psHeaderContainsUnlockedView() {
+        Bitmap lockImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_lock_button));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_ENABLED);
+        when(mPrivateProfileManager.isPrivateSpaceSettingsButtonVisible()).thenReturn(false);
+
+        mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+
+        int totalContainerHeaderView = 0;
+        int totalLockUnlockButtonView = 0;
+        int totalSettingsImageView = 0;
+        for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+            View view = mPsHeaderLayout.getChildAt(i);
+            if (view.getId() == R.id.ps_container_header) {
+                totalContainerHeaderView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+            } else if (view.getId() == R.id.ps_lock_unlock_button
+                    && view instanceof ImageView imageView) {
+                totalLockUnlockButtonView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(lockImage);
+            } else {
+                assertEquals(View.GONE, view.getVisibility());
+            }
+        }
+        assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+        assertEquals(LOCK_UNLOCK_BUTTON_COUNT, totalLockUnlockButtonView);
+        assertEquals(PS_SETTINGS_BUTTON_COUNT_INVISIBLE, totalSettingsImageView);
+    }
+
+    @Test
+    public void privateProfileTransitioning_psHeaderContainsTransitionView() {
+        Bitmap transitionImage = getBitmap(mContext.getDrawable(R.drawable.bg_ps_transition_image));
+        when(mPrivateProfileManager.getCurrentState()).thenReturn(STATE_TRANSITION);
+
+        mPsHeaderViewController.addPrivateSpaceHeaderViewElements(mPsHeaderLayout);
+
+        int totalContainerHeaderView = 0;
+        int totalLockUnlockButtonView = 0;
+        for (int i = 0; i < mPsHeaderLayout.getChildCount(); i++) {
+            View view = mPsHeaderLayout.getChildAt(i);
+            if (view.getId() == R.id.ps_container_header) {
+                totalContainerHeaderView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+            } else if (view.getId() == R.id.ps_transition_image
+                    && view instanceof ImageView imageView) {
+                totalLockUnlockButtonView += 1;
+                assertEquals(View.VISIBLE, view.getVisibility());
+                getBitmap(imageView.getDrawable()).sameAs(transitionImage);
+            } else {
+                assertEquals(View.GONE, view.getVisibility());
+            }
+        }
+        assertEquals(CONTAINER_HEADER_ELEMENT_COUNT, totalContainerHeaderView);
+        assertEquals(PS_TRANSITION_IMAGE_COUNT, totalLockUnlockButtonView);
+    }
+
+    private Bitmap getBitmap(Drawable drawable) {
+        Bitmap result;
+        if (drawable instanceof BitmapDrawable) {
+            result = ((BitmapDrawable) drawable).getBitmap();
+        } else {
+            int width = drawable.getIntrinsicWidth();
+            int height = drawable.getIntrinsicHeight();
+            // Some drawables have no intrinsic width - e.g. solid colours.
+            if (width <= 0) {
+                width = 1;
+            }
+            if (height <= 0) {
+                height = 1;
+            }
+
+            result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(result);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
+        }
+        return result;
+    }
+}
diff --git a/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
new file mode 100644
index 0000000..fe9464a
--- /dev/null
+++ b/tests/src/com/android/launcher3/allapps/TaplKeyboardFocusTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import static com.android.launcher3.ui.TaplTestsLauncher3.initialize;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowInsets;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TaplKeyboardFocusTest extends AbstractLauncherUiTest {
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        initialize(this);
+    }
+
+    @Test
+    public void testAllAppsFocusApp() {
+        final HomeAllApps allApps = mLauncher.goHome().switchToAllApps();
+        assertTrue("Launcher internal state is not All Apps",
+                isInState(() -> LauncherState.ALL_APPS));
+        allApps.freeze();
+        try {
+            mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
+            executeOnLauncher(launcher -> assertNotNull("No focused child.",
+                    launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()));
+        } finally {
+            allApps.unfreeze();
+        }
+    }
+
+    @Test
+    public void testAllAppsExitSearchAndFocusApp() {
+        final HomeAllApps allApps = mLauncher.goHome().switchToAllApps();
+        assertTrue("Launcher internal state is not All Apps",
+                isInState(() -> LauncherState.ALL_APPS));
+        allApps.freeze();
+        try {
+            executeOnLauncher(launcher -> launcher.getAppsView().getSearchView().requestFocus());
+            waitForLauncherCondition("Search view does not have focus.",
+                    launcher -> launcher.getAppsView().getSearchView().hasFocus());
+
+            mLauncher.pressAndHoldKeyCode(KeyEvent.KEYCODE_DPAD_DOWN, 0);
+            executeOnLauncher(launcher -> assertNotNull("No focused child.",
+                    launcher.getAppsView().getActiveRecyclerView().getApps().getFocusedChild()));
+        } finally {
+            allApps.unfreeze();
+        }
+    }
+
+    @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 ed846ed..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;
@@ -91,7 +92,7 @@
 import java.util.function.Supplier;
 
 /**
- * Base class for all instrumentation tests providing various utility methods. RUN 4
+ * Base class for all instrumentation tests providing various utility methods.
  */
 public abstract class AbstractLauncherUiTest {
 
@@ -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);
                 }
@@ -187,7 +199,7 @@
             Utilities.enableRunningInTestHarnessForTests();
             mLauncher.setSystemHealthSupplier(startTime -> TestCommandReceiver.callCommand(
                             TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE, startTime.toString())
-                            .getString("result"));
+                    .getString("result"));
             mLauncher.setOnSettledStateAction(
                     containerType -> executeOnLauncher(
                             launcher ->
@@ -248,12 +260,6 @@
     public void setUp() throws Exception {
         mLauncher.onTestStart();
 
-        if (TestStabilityRule.isPresubmit()) {
-            aggressivelyUnlockSysUi();
-        } else {
-            verifyKeyguardInvisible();
-        }
-
         final String launcherPackageName = mDevice.getLauncherPackageName();
         try {
             final Context context = InstrumentationRegistry.getContext();
@@ -284,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(
@@ -308,7 +331,27 @@
         Log.d(TAG, "Keyguard is not visible");
     }
 
-    public static void verifyKeyguardInvisible() {
+    /** Waits for setup wizard to go away. */
+    private static void waitForSetupWizardDismissal() {
+        if (!TestStabilityRule.isPresubmit()) return;
+
+        if (sFirstTimeWaitingForWizard) {
+            try {
+                getUiDevice().executeShellCommand(
+                        "am force-stop com.google.android.setupwizard");
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        final boolean wizardDismissed = TestHelpers.wait(
+                Until.gone(By.pkg("com.google.android.setupwizard").depth(0)),
+                sFirstTimeWaitingForWizard ? 120000 : 0);
+        sFirstTimeWaitingForWizard = false;
+        Assert.assertTrue("Setup wizard is still visible", wizardDismissed);
+    }
+
+    private static void verifyKeyguardInvisible() {
         final boolean keyguardAlreadyVisible = sSeenKeyguard;
 
         sSeenKeyguard = sSeenKeyguard
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/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index a85b6bd..770fe14 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -25,6 +25,7 @@
 import androidx.test.uiautomator.UiObject2;
 
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -201,7 +202,8 @@
             OverviewTask task = getCurrentTask();
             mLauncher.assertNotNull("current task is null", task);
             mLauncher.scrollLeftByDistance(verifyActiveContainer(),
-                    task.getVisibleWidth() + mLauncher.getOverviewPageSpacing());
+                    mLauncher.getRealDisplaySize().x - task.getUiObject().getVisibleBounds().left
+                            + mLauncher.getOverviewPageSpacing());
 
             try (LauncherInstrumentation.Closable c2 =
                          mLauncher.addContextLayer("scrolled task off screen")) {
@@ -231,14 +233,14 @@
         final List<UiObject2> taskViews = getTasks();
         mLauncher.assertNotEquals("Unable to find a task", 0, taskViews.size());
 
-        // taskViews contains up to 3 task views: the 'main' (having the widest visible part) one
-        // in the center, and parts of its right and left siblings. Find the main task view by
-        // its width.
-        final UiObject2 widestTask = Collections.max(taskViews,
-                (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(),
-                        mLauncher.getVisibleBounds(t2).width()));
-
-        return new OverviewTask(mLauncher, widestTask, this);
+        final List<OverviewTask> overviewTasks = taskViews.stream().map(
+                task -> new OverviewTask(mLauncher, task, this)).toList();
+        // The widest, and most top-right task should be the current task
+        return Collections.max(overviewTasks,
+                Comparator.comparingInt(OverviewTask::getVisibleWidth)
+                        .thenComparingInt(OverviewTask::getTaskCenterX)
+                        .thenComparing(
+                                Comparator.comparing(OverviewTask::getTaskCenterY).reversed()));
     }
 
     /** Returns an overview task matching TestActivity {@param activityNumber}. */
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 307f192..e30885b 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -564,6 +564,9 @@
         if (hasSystemLauncherObject(OVERVIEW_RES_ID)) return "Overview";
         if (hasLauncherObject(WORKSPACE_RES_ID)) return "Workspace";
         if (hasLauncherObject(APPS_RES_ID)) return "AllApps";
+        if (mDevice.hasObject(By.pkg(getLauncherPackageName()).depth(0))) {
+            return "<Launcher in invalid state>";
+        }
         return "LaunchedApp (" + getVisiblePackages() + ")";
     }
 
@@ -1608,8 +1611,11 @@
         scroll(
                 container,
                 Direction.LEFT,
-                new Rect(leftGestureMargin, 0,
-                        containerRect.width() - distance - rightGestureMarginInContainer, 0),
+                new Rect(leftGestureMargin,
+                        0,
+                        Math.max(containerRect.width() - distance - leftGestureMargin,
+                                rightGestureMarginInContainer),
+                        0),
                 10,
                 true);
     }
@@ -1782,7 +1788,7 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
-    boolean isGridOnlyOverviewEnabled() {
+    public boolean isGridOnlyOverviewEnabled() {
         return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW).getBoolean(
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
@@ -2060,6 +2066,7 @@
     }
 
     // TODO(b/270393900): Remove with ENABLE_ALL_APPS_SEARCH_IN_TASKBAR flag cleanup.
+
     /** Refreshes the known overview target in TIS. */
     public void refreshOverviewTarget() {
         getTestInfo(TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET);
@@ -2225,7 +2232,7 @@
             int bottomBound = Math.min(
                     containerBounds.bottom,
                     getRealDisplaySize().y - getImeInsets().bottom);
-            int y = (bottomBound - containerBounds.top) / 2;
+            int y = (bottomBound + containerBounds.top) / 2;
             // Do not tap in the status bar.
             y = Math.max(y, getWindowInsets().top);
 
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 06fac48..8a34f0d 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -73,10 +73,13 @@
      * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider.
      */
     private int getCombinedSplitTaskHeight() {
-        UiObject2 taskSnapshot1 =
-                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_1);
-        UiObject2 taskSnapshot2 =
-                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_2);
+        UiObject2 taskSnapshot1 = findObjectInTask(TASK_SNAPSHOT_1);
+        UiObject2 taskSnapshot2 = findObjectInTask(TASK_SNAPSHOT_2);
+
+        // If the split task is partly off screen, taskSnapshot1 can be invisible.
+        if (taskSnapshot1 == null) {
+            return taskSnapshot2.getVisibleBounds().height();
+        }
 
         int top = Math.min(
                 taskSnapshot1.getVisibleBounds().top, taskSnapshot2.getVisibleBounds().top);
@@ -102,10 +105,8 @@
      * Calculates the visible width for split tasks, containing 2 snapshot tiles and a divider.
      */
     private int getCombinedSplitTaskWidth() {
-        UiObject2 taskSnapshot1 =
-                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_1);
-        UiObject2 taskSnapshot2 =
-                mLauncher.findObjectInContainer(mTask.getParent(), TASK_SNAPSHOT_2);
+        UiObject2 taskSnapshot1 = findObjectInTask(TASK_SNAPSHOT_1);
+        UiObject2 taskSnapshot2 = findObjectInTask(TASK_SNAPSHOT_2);
 
         int left = Math.min(
                 taskSnapshot1.getVisibleBounds().left, taskSnapshot2.getVisibleBounds().left);
@@ -116,11 +117,15 @@
     }
 
     int getTaskCenterX() {
-        return mTask.getVisibleCenter().x;
+        return mTask.getParent().getVisibleCenter().x;
+    }
+
+    int getTaskCenterY() {
+        return mTask.getParent().getVisibleCenter().y;
     }
 
     float getExactCenterX() {
-        return mTask.getVisibleBounds().exactCenterX();
+        return mTask.getParent().getVisibleBounds().exactCenterX();
     }
 
     UiObject2 getUiObject() {
@@ -144,7 +149,8 @@
 
             boolean taskWasFocused = mLauncher.isTablet() && getVisibleHeight() == mLauncher
                     .getFocusedTaskHeightForTablet();
-            List<Integer> originalTasksCenterX = getCurrentTasksCenterXList();
+            List<Integer> originalTasksCenterX =
+                    getCurrentTasksCenterXList().stream().sorted().toList();
             boolean isClearAllVisibleBeforeDismiss = mOverview.isClearAllVisible();
 
             dismissBySwipingUp();
@@ -155,7 +161,8 @@
                             mOverview.getFocusedTaskForTablet());
                 }
                 if (!isClearAllVisibleBeforeDismiss) {
-                    List<Integer> currentTasksCenterX = getCurrentTasksCenterXList();
+                    List<Integer> currentTasksCenterX =
+                            getCurrentTasksCenterXList().stream().sorted().toList();
                     if (originalTasksCenterX.size() == currentTasksCenterX.size()) {
                         // Check for the same number of visible tasks before and after to
                         // avoid asserting on cases of shifting all tasks to close the distance
@@ -173,7 +180,7 @@
         // Dismiss the task via flinging it up.
         final Rect taskBounds = mLauncher.getVisibleBounds(mTask);
         final int centerX = taskBounds.centerX();
-        final int centerY = taskBounds.centerY();
+        final int centerY = taskBounds.bottom - 1;
         mLauncher.executeAndWaitForLauncherEvent(
                 () -> mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false,
                         LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER),
@@ -253,6 +260,10 @@
     }
 
     boolean isTaskSplit() {
-        return mLauncher.findObjectInContainer(mTask.getParent(), "bottomright_snapshot") != null;
+        return findObjectInTask(TASK_SNAPSHOT_2) != null;
+    }
+
+    private UiObject2 findObjectInTask(String resName) {
+        return mTask.getParent().findObject(mLauncher.getOverviewObjectSelector(resName));
     }
 }