Merge "[Launcher Jank] Avoid making ContentResolver binder calls from GestureNavigationSettingsObserver" into main
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index ff97b22..9051ca8 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -5,7 +5,7 @@
 ktfmt = --kotlinlang-style
 
 [Tool Paths]
-ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar
+ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
 
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index fecc43d..f1f9966 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -79,6 +79,13 @@
 }
 
 flag {
+    name: "enable_taskbar_customization"
+    namespace: "launcher"
+    description: "Enables taskbar customization framework."
+    bug: "347281365"
+}
+
+flag {
     name: "enable_unfolded_two_pane_picker"
     namespace: "launcher"
     description: "Enables two pane widget picker for unfolded foldables"
@@ -302,3 +309,10 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "floating_search_bar"
+    namespace: "launcher"
+    description: "Search bar persists at the bottom of the screen across Launcher states"
+    bug: "346408388"
+}
diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml
index 7c55bf8..f3c3383 100644
--- a/quickstep/res/layout/transient_taskbar.xml
+++ b/quickstep/res/layout/transient_taskbar.xml
@@ -45,8 +45,6 @@
         android:layout_gravity="bottom|end"
         android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
         android:paddingTop="@dimen/bubblebar_pointer_visible_size"
-        android:paddingEnd="@dimen/taskbar_icon_spacing"
-        android:paddingStart="@dimen/taskbar_icon_spacing"
         android:visibility="gone"
         android:gravity="center"
         android:clipChildren="false"
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 0000c0d..9178062 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -35,7 +35,7 @@
 
 /** Manage recents related operations with desktop tasks */
 class DesktopRecentsTransitionController(
-    private val stateManager: StateManager<*>,
+    private val stateManager: StateManager<*, *>,
     private val systemUiProxy: SystemUiProxy,
     private val appThread: IApplicationThread,
     private val depthController: DepthController?
@@ -64,7 +64,7 @@
 
     private class RemoteDesktopLaunchTransitionRunner(
         private val desktopTaskView: DesktopTaskView,
-        private val stateManager: StateManager<*>,
+        private val stateManager: StateManager<*, *>,
         private val depthController: DepthController?,
         private val successCallback: Consumer<Boolean>?
     ) : RemoteTransitionStub() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 74554a2..6b62c86 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -62,7 +62,6 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.Gravity;
-import android.view.RoundedCorner;
 import android.view.Surface;
 import android.view.View;
 import android.view.WindowInsets;
@@ -166,7 +165,6 @@
     private final TaskbarControllers mControllers;
 
     private final WindowManager mWindowManager;
-    private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
     private DeviceProfile mDeviceProfile;
     private WindowManager.LayoutParams mWindowLayoutParams;
     private boolean mIsFullscreen;
@@ -227,16 +225,8 @@
         Context c = getApplicationContext();
         mWindowManager = c.getSystemService(WindowManager.class);
 
-        boolean phoneMode = isPhoneMode();
-        mLeftCorner = phoneMode
-                ? null
-                : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
-        mRightCorner = phoneMode
-                ? null
-                : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
-
         // Inflate views.
-        int taskbarLayout = DisplayController.isTransientTaskbar(this) && !phoneMode
+        int taskbarLayout = DisplayController.isTransientTaskbar(this) && !isPhoneMode()
                 ? R.layout.transient_taskbar
                 : R.layout.taskbar;
         mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
@@ -615,12 +605,9 @@
         return mImeDrawsImeNavBar;
     }
 
-    public int getLeftCornerRadius() {
-        return mLeftCorner == null ? 0 : mLeftCorner.getRadius();
-    }
-
-    public int getRightCornerRadius() {
-        return mRightCorner == null ? 0 : mRightCorner.getRadius();
+    public int getCornerRadius() {
+        return isPhoneMode() ? 0 : getResources().getDimensionPixelSize(
+                R.dimen.persistent_taskbar_corner_radius);
     }
 
     public WindowManager.LayoutParams getWindowLayoutParams() {
@@ -1013,7 +1000,7 @@
 
 
         return mDeviceProfile.taskbarHeight
-                + Math.max(getLeftCornerRadius(), getRightCornerRadius())
+                + getCornerRadius()
                 + extraHeightForTaskbarTooltips;
     }
 
@@ -1602,4 +1589,9 @@
     boolean canToggleHomeAllApps() {
         return mControllers.uiController.canToggleHomeAllApps();
     }
+
+    @VisibleForTesting
+    public TaskbarControllers getControllers() {
+        return mControllers;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index bafd059..2737cbd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -37,8 +37,6 @@
 class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
 
     private val isInSetup: Boolean = !context.isUserSetupComplete
-    private val DARK_THEME_SHADOW_ALPHA = 51f
-    private val LIGHT_THEME_SHADOW_ALPHA = 25f
 
     private val maxTransientTaskbarHeight =
         context.transientTaskbarDeviceProfile.taskbarHeight.toFloat()
@@ -54,6 +52,7 @@
     var isAnimatingPinning = false
 
     val paint = Paint()
+    private val strokePaint = Paint()
     val lastDrawnTransientRect = RectF()
     var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
     var translationYForSwipe = 0f
@@ -62,19 +61,18 @@
     private val transientBackgroundBounds = context.transientTaskbarBounds
 
     private val shadowAlpha: Float
+    private val strokeAlpha: Int
     private var shadowBlur = 0f
     private var keyShadowDistance = 0f
     private var bottomMargin = 0
 
-    private val fullLeftCornerRadius = context.leftCornerRadius.toFloat()
-    private val fullRightCornerRadius = context.rightCornerRadius.toFloat()
-    private var leftCornerRadius = fullLeftCornerRadius
-    private var rightCornerRadius = fullRightCornerRadius
+    private val fullCornerRadius = context.cornerRadius.toFloat()
+    private var cornerRadius = fullCornerRadius
     private var widthInsetPercentage = 0f
-    private val square: Path = Path()
-    private val circle: Path = Path()
-    private val invertedLeftCornerPath: Path = Path()
-    private val invertedRightCornerPath: Path = Path()
+    private val square = Path()
+    private val circle = Path()
+    private val invertedLeftCornerPath = Path()
+    private val invertedRightCornerPath = Path()
 
     private var stashedHandleWidth =
         context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width)
@@ -86,10 +84,18 @@
         paint.color = context.getColor(R.color.taskbar_background)
         paint.flags = Paint.ANTI_ALIAS_FLAG
         paint.style = Paint.Style.FILL
-
-        shadowAlpha =
-            if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
-            else LIGHT_THEME_SHADOW_ALPHA
+        strokePaint.color = context.getColor(R.color.taskbar_stroke)
+        strokePaint.flags = Paint.ANTI_ALIAS_FLAG
+        strokePaint.style = Paint.Style.STROKE
+        strokePaint.strokeWidth =
+            context.resources.getDimension(R.dimen.transient_taskbar_stroke_width)
+        if (Utilities.isDarkTheme(context)) {
+            strokeAlpha = DARK_THEME_STROKE_ALPHA
+            shadowAlpha = DARK_THEME_SHADOW_ALPHA
+        } else {
+            strokeAlpha = LIGHT_THEME_STROKE_ALPHA
+            shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
+        }
 
         setCornerRoundness(DEFAULT_ROUNDNESS)
     }
@@ -106,7 +112,7 @@
     }
 
     /**
-     * Sets the roundness of the round corner above Taskbar. No effect on transient Taskkbar.
+     * Sets the roundness of the round corner above Taskbar. No effect on transient Taskbar.
      *
      * @param cornerRoundness 0 has no round corner, 1 has complete round corner.
      */
@@ -115,21 +121,18 @@
             return
         }
 
-        leftCornerRadius = fullLeftCornerRadius * cornerRoundness
-        rightCornerRadius = fullRightCornerRadius * cornerRoundness
+        cornerRadius = fullCornerRadius * cornerRoundness
 
         // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
         // square, and then subtract out a circle from the appropriate corner.
         square.reset()
-        square.addRect(0f, 0f, leftCornerRadius, leftCornerRadius, Path.Direction.CW)
+        square.addRect(0f, 0f, cornerRadius, cornerRadius, Path.Direction.CW)
         circle.reset()
-        circle.addCircle(leftCornerRadius, 0f, leftCornerRadius, Path.Direction.CW)
+        circle.addCircle(cornerRadius, 0f, cornerRadius, Path.Direction.CW)
         invertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE)
 
-        square.reset()
-        square.addRect(0f, 0f, rightCornerRadius, rightCornerRadius, Path.Direction.CW)
         circle.reset()
-        circle.addCircle(0f, 0f, rightCornerRadius, Path.Direction.CW)
+        circle.addCircle(0f, 0f, cornerRadius, Path.Direction.CW)
         invertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE)
     }
 
@@ -163,10 +166,10 @@
         }
 
         // Draw the inverted rounded corners above the taskbar.
-        canvas.translate(0f, -leftCornerRadius)
+        canvas.translate(0f, -cornerRadius)
         canvas.drawPath(invertedLeftCornerPath, paint)
-        canvas.translate(0f, leftCornerRadius)
-        canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
+        canvas.translate(0f, cornerRadius)
+        canvas.translate(canvas.width - cornerRadius, -cornerRadius)
         canvas.drawPath(invertedRightCornerPath, paint)
     }
 
@@ -236,6 +239,7 @@
             keyShadowDistance,
             setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
         )
+        strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
 
         lastDrawnTransientRect.set(
             transientBackgroundBounds.left + halfWidthDelta,
@@ -247,6 +251,7 @@
         lastDrawnTransientRect.inset(horizontalInset, 0f)
 
         canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
+        canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, strokePaint)
     }
 
     /**
@@ -259,5 +264,9 @@
 
     companion object {
         const val DEFAULT_ROUNDNESS = 1f
+        private const val DARK_THEME_STROKE_ALPHA = 51
+        private const val LIGHT_THEME_STROKE_ALPHA = 41
+        private const val DARK_THEME_SHADOW_ALPHA = 51f
+        private const val LIGHT_THEME_SHADOW_ALPHA = 25f
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ec2cee2..2a58db2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -611,7 +611,8 @@
         }
     }
 
-    private void addTaskbarRootViewToWindow() {
+    @VisibleForTesting
+    void addTaskbarRootViewToWindow() {
         if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
             mWindowManager.addView(mTaskbarRootLayout,
                     mTaskbarActivityContext.getWindowLayoutParams());
@@ -619,7 +620,8 @@
         }
     }
 
-    private void removeTaskbarRootViewFromWindow() {
+    @VisibleForTesting
+    void removeTaskbarRootViewFromWindow() {
         if (enableTaskbarNoRecreate() && mAddedWindow) {
             mWindowManager.removeViewImmediate(mTaskbarRootLayout);
             mAddedWindow = false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index 0946caf..b1fc9cc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -46,8 +46,11 @@
         enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps()
 
     // TODO(b/343532825): Add a setting to disable Recents even when the flag is on.
-    @VisibleForTesting
-    var isEnabled = enableRecentsInTaskbar() || canShowRunningApps
+    var isEnabled: Boolean = enableRecentsInTaskbar() || canShowRunningApps
+        @VisibleForTesting
+        set(isEnabledFromTest){
+            field = isEnabledFromTest
+        }
 
     // Initialized in init.
     private lateinit var controllers: TaskbarControllers
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index bc13c89..25939e1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -123,22 +123,23 @@
         )
         // Create background path
         val backgroundPath = Path()
+        val topOffset = backgroundHeight - bounds.height().toFloat()
         val radius = backgroundHeight / 2f
         val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width)
         val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat())
-        val top = bounds.top + arrowVisibleHeight
+        val top = bounds.top - topOffset + arrowVisibleHeight
+
         val bottom = bounds.top + bounds.height().toFloat()
         backgroundPath.addRoundRect(left, top, right, bottom, radius, radius, Path.Direction.CW)
-        addArrowPathIfNeeded(backgroundPath)
+        addArrowPathIfNeeded(backgroundPath, topOffset)
 
         // Draw background.
         canvas.drawPath(backgroundPath, fillPaint)
         canvas.drawPath(backgroundPath, strokePaint)
-
         canvas.restore()
     }
 
-    private fun addArrowPathIfNeeded(sourcePath: Path) {
+    private fun addArrowPathIfNeeded(sourcePath: Path, topOffset: Float) {
         if (!showingArrow || arrowHeightFraction <= 0) return
         val arrowPath = Path()
         RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
@@ -153,7 +154,7 @@
         arrowPath.transform(pathTransform)
         // shift to arrow position
         val arrowStart = bounds.left + arrowPositionX - (arrowWidth / 2f)
-        val arrowTop = (1 - arrowHeightFraction) * arrowVisibleHeight
+        val arrowTop = (1 - arrowHeightFraction) * arrowVisibleHeight - topOffset
         arrowPath.offset(arrowStart, arrowTop)
         // union with rectangle
         sourcePath.op(arrowPath, Path.Op.UNION)
@@ -180,7 +181,7 @@
         fillPaint.colorFilter = colorFilter
     }
 
-    fun setHeight(newHeight: Float) {
+    fun setBackgroundHeight(newHeight: Float) {
         backgroundHeight = newHeight
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 046f5b6..028df34 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -135,7 +135,6 @@
 
     private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
             new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
-    private final Executor mMainExecutor;
     private final LauncherApps mLauncherApps;
     private final BubbleIconFactory mIconFactory;
     private final SystemUiProxy mSystemUiProxy;
@@ -198,7 +197,6 @@
         if (sBubbleBarEnabled) {
             mSystemUiProxy.setBubblesListener(this);
         }
-        mMainExecutor = MAIN_EXECUTOR;
         mLauncherApps = context.getSystemService(LauncherApps.class);
         mIconFactory = new BubbleIconFactory(context,
                 context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size),
@@ -241,7 +239,7 @@
     private void createAndAddOverflowIfNeeded() {
         if (mOverflowBubble == null) {
             BubbleBarOverflow overflow = createOverflow(mContext);
-            mMainExecutor.execute(() -> {
+            MAIN_EXECUTOR.execute(() -> {
                 // we're on the main executor now, so check that the overflow hasn't been created
                 // again to avoid races.
                 if (mOverflowBubble == null) {
@@ -303,12 +301,12 @@
                     }
                     viewUpdate.currentBubbles = currentBubbles;
                 }
-                mMainExecutor.execute(() -> applyViewChanges(viewUpdate));
+                MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate));
             });
         } else {
             // No bubbles to load, immediately apply the changes.
             BUBBLE_STATE_EXECUTOR.execute(
-                    () -> mMainExecutor.execute(() -> applyViewChanges(viewUpdate)));
+                    () -> MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate)));
         }
     }
 
@@ -496,7 +494,7 @@
 
     @Override
     public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
-        mMainExecutor.execute(
+        MAIN_EXECUTOR.execute(
                 () -> mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation));
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 2efecfb..c7c63e8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -24,6 +24,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -44,6 +45,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.util.DisplayController;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 
 import java.util.List;
@@ -85,6 +87,7 @@
     private static final int MAX_VISIBLE_BUBBLES_COLLAPSED = 2;
     private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
     private static final int WIDTH_ANIMATION_DURATION_MS = 200;
+    private static final int SCALE_ANIMATION_DURATION_MS = 200;
 
     private static final long FADE_OUT_ANIM_ALPHA_DURATION_MS = 50L;
     private static final long FADE_OUT_ANIM_ALPHA_DELAY_MS = 50L;
@@ -135,13 +138,12 @@
     private float mBubbleBarPadding;
     // The size of a bubble in the bar
     private float mIconSize;
+    // The scale of bubble icons
+    private float mIconScale = 1f;
     // The elevation of the bubbles within the bar
     private final float mBubbleElevation;
     private final float mDragElevation;
     private final int mPointerSize;
-
-    private final Rect mTempBackgroundBounds = new Rect();
-
     // Whether the bar is expanded (i.e. the bubble activity is being displayed).
     private boolean mIsBarExpanded = false;
     // The currently selected bubble view.
@@ -162,7 +164,8 @@
     /** An animator used for scaling in a new bubble to the bubble bar while expanded. */
     @Nullable
     private ValueAnimator mNewBubbleScaleInAnimator = null;
-
+    @Nullable
+    private ValueAnimator mScalePaddingAnimator;
     @Nullable
     private Animator mBubbleBarLocationAnimator = null;
 
@@ -217,42 +220,74 @@
         setBackgroundDrawable(mBubbleBarBackground);
 
         mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
-        mWidthAnimator.addUpdateListener(animation -> {
-            updateBubblesLayoutProperties(mBubbleBarLocation);
-            invalidate();
-        });
-        mWidthAnimator.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-            }
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBubbleBarBackground.showArrow(mIsBarExpanded);
-                if (!mIsBarExpanded && mReorderRunnable != null) {
-                    mReorderRunnable.run();
-                    mReorderRunnable = null;
-                }
-                // If the bar was just collapsed and the overflow was the last bubble that was
-                // selected, set the first bubble as selected.
-                if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
-                        && mSelectedBubbleView != null
-                        && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
-                    BubbleView firstBubble = (BubbleView) getChildAt(0);
-                    mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
-                }
-                updateWidth();
-            }
+        addAnimationCallBacks(mWidthAnimator,
+                /* onStart= */ () -> mBubbleBarBackground.showArrow(true),
+                /* onEnd= */ () -> {
+                    mBubbleBarBackground.showArrow(mIsBarExpanded);
+                    if (!mIsBarExpanded && mReorderRunnable != null) {
+                        mReorderRunnable.run();
+                        mReorderRunnable = null;
+                    }
+                    // If the bar was just collapsed and the overflow was the last bubble that was
+                    // selected, set the first bubble as selected.
+                    if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
+                            && mSelectedBubbleView != null
+                            && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
+                        BubbleView firstBubble = (BubbleView) getChildAt(0);
+                        mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
+                    }
+                    updateWidth();
+                },
+                /* onUpdate= */ animator -> {
+                    updateBubblesLayoutProperties(mBubbleBarLocation);
+                    invalidate();
+                });
+    }
 
-            @Override
-            public void onAnimationRepeat(Animator animation) {
-            }
 
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mBubbleBarBackground.showArrow(true);
-            }
-        });
+    /**
+     * Animates icon sizes and spacing between icons and bubble bar borders.
+     *
+     * @param newIconSize         new icon size
+     * @param newBubbleBarPadding spacing between icons and bubble bar borders.
+     */
+    public void animateBubbleBarIconSize(float newIconSize, float newBubbleBarPadding) {
+        if (!isIconSizeOrPaddingUpdated(newIconSize, newBubbleBarPadding)) {
+            return;
+        }
+        if (!Flags.animateBubbleSizeChange()) {
+            setIconSizeAndPadding(newIconSize, newBubbleBarPadding);
+        }
+        if (mScalePaddingAnimator != null && mScalePaddingAnimator.isRunning()) {
+            mScalePaddingAnimator.cancel();
+        }
+        ValueAnimator scalePaddingAnimator = ValueAnimator.ofFloat(0f, 1f);
+        scalePaddingAnimator.setDuration(SCALE_ANIMATION_DURATION_MS);
+        boolean isPaddingUpdated = isPaddingUpdated(newBubbleBarPadding);
+        boolean isIconSizeUpdated = isIconSizeUpdated(newIconSize);
+        float initialScale = mIconScale;
+        float initialPadding = mBubbleBarPadding;
+        float targetScale = newIconSize / getScaledIconSize();
+
+        addAnimationCallBacks(scalePaddingAnimator,
+                /* onStart= */ null,
+                /* onEnd= */ () -> setIconSizeAndPadding(newIconSize, newBubbleBarPadding),
+                /* onUpdate= */ animator -> {
+                    float transitionProgress = (float) animator.getAnimatedValue();
+                    if (isIconSizeUpdated) {
+                        mIconScale =
+                                initialScale + (targetScale - initialScale) * transitionProgress;
+                    }
+                    if (isPaddingUpdated) {
+                        mBubbleBarPadding = initialPadding
+                                + (newBubbleBarPadding - initialPadding) * transitionProgress;
+                    }
+                    updateBubblesLayoutProperties(mBubbleBarLocation);
+                    invalidate();
+                });
+        scalePaddingAnimator.start();
+        mScalePaddingAnimator = scalePaddingAnimator;
     }
 
     @Override
@@ -267,28 +302,37 @@
     }
 
     /**
-     * Sets new icon size and spacing between icons and bubble bar borders.
+     * Sets new icon sizes and newBubbleBarPadding between icons and bubble bar borders.
      *
-     * @param newIconSize new icon size
-     * @param spacing     spacing between icons and bubble bar borders.
+     * @param newIconSize         new icon size
+     * @param newBubbleBarPadding newBubbleBarPadding between icons and bubble bar borders.
      */
-    // TODO(b/335575529): animate bubble bar icons size change
-    public void setIconSizeAndPadding(float newIconSize, float spacing) {
+    public void setIconSizeAndPadding(float newIconSize, float newBubbleBarPadding) {
         // TODO(b/335457839): handle new bubble animation during the size change
-        mBubbleBarPadding = spacing;
+        if (!isIconSizeOrPaddingUpdated(newIconSize, newBubbleBarPadding)) {
+            return;
+        }
+        mIconScale = 1f;
+        mBubbleBarPadding = newBubbleBarPadding;
         mIconSize = newIconSize;
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View childView = getChildAt(i);
+            childView.setScaleY(mIconScale);
+            childView.setScaleY(mIconScale);
             FrameLayout.LayoutParams params = (LayoutParams) childView.getLayoutParams();
             params.height = (int) mIconSize;
             params.width = (int) mIconSize;
             childView.setLayoutParams(params);
         }
-        mBubbleBarBackground.setHeight(getBubbleBarExpandedHeight());
+        mBubbleBarBackground.setBackgroundHeight(getBubbleBarHeight());
         updateLayoutParams();
     }
 
+    private float getScaledIconSize() {
+        return mIconSize * mIconScale;
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -698,6 +742,11 @@
         setLayoutParams(lp);
     }
 
+    private float getBubbleBarHeight() {
+        return mIsBarExpanded ? getBubbleBarExpandedHeight()
+                : getBubbleBarCollapsedHeight();
+    }
+
     /** @return the horizontal margin between the bubble bar and the edge of the screen. */
     int getHorizontalMargin() {
         LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
@@ -714,7 +763,10 @@
         final float expandedWidth = expandedWidth();
         final float collapsedWidth = collapsedWidth();
         int bubbleCount = getChildCount();
-        final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+        float viewBottom = mBubbleBarBounds.height() + (isExpanded() ? mPointerSize : 0);
+        float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
+        // When translating X & Y the scale is ignored, so need to deduct it from the translations
+        final float ty = bubbleBarAnimatedTop + mBubbleBarPadding - getScaleIconShift();
         final boolean animate = getVisibility() == VISIBLE;
         final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
         // elevation state is opposite to widthState - when expanded all icons are flat
@@ -729,8 +781,9 @@
             bv.setDragTranslationX(0f);
             bv.setOffsetX(0f);
 
+            bv.setScaleX(mIconScale);
+            bv.setScaleY(mIconScale);
             bv.setTranslationY(ty);
-
             // the position of the bubble when the bar is fully expanded
             final float expandedX = getExpandedBubbleTranslationX(i, bubbleCount, onLeft);
             // the position of the bubble when the bar is fully collapsed
@@ -795,21 +848,28 @@
         mBubbleBarBackground.setArrowPosition(arrowPosition);
         mBubbleBarBackground.setArrowHeightFraction(widthState);
         mBubbleBarBackground.setWidth(interpolatedWidth);
+        mBubbleBarBackground.setBackgroundHeight(getBubbleBarExpandedHeight());
+    }
+
+    private float getScaleIconShift() {
+        return (mIconSize - getScaledIconSize()) / 2;
     }
 
     private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
         if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
             return 0;
         }
-        final float iconAndSpacing = mIconSize + mExpandedBarIconsSpacing;
+        final float iconAndSpacing = getScaledIconSize() + mExpandedBarIconsSpacing;
+        float translationX;
         if (mNewBubbleScaleInAnimator != null && mNewBubbleScaleInAnimator.isRunning()) {
-            return getExpandedBubbleTranslationXDuringScaleAnimation(
+            translationX = getExpandedBubbleTranslationXDuringScaleAnimation(
                     bubbleIndex, bubbleCount, onLeft);
         } else if (onLeft) {
-            return (bubbleCount - bubbleIndex - 1) * iconAndSpacing;
+            translationX = mBubbleBarPadding + (bubbleCount - bubbleIndex - 1) * iconAndSpacing;
         } else {
-            return bubbleIndex * iconAndSpacing;
+            translationX = mBubbleBarPadding + bubbleIndex * iconAndSpacing;
         }
+        return translationX - getScaleIconShift();
     }
 
     /**
@@ -830,17 +890,17 @@
             // compiler doesn't know that.
             return 0;
         }
-        final float iconAndSpacing = mIconSize + mExpandedBarIconsSpacing;
+        final float iconAndSpacing = getScaledIconSize() + mExpandedBarIconsSpacing;
         final float newBubbleScale = mNewBubbleScaleInAnimator.getAnimatedFraction();
         // the new bubble is scaling in from the center, so we need to adjust its translation so
         // that the distance to the adjacent bubble scales at the same rate.
-        final float pivotAdjustment = -(1 - newBubbleScale) * mIconSize / 2f;
+        final float pivotAdjustment = -(1 - newBubbleScale) * getScaledIconSize() / 2f;
 
         if (onLeft) {
             if (bubbleIndex == 0) {
                 // this is the animating bubble. use scaled spacing between it and the bubble to
                 // its left
-                return (bubbleCount - 1) * mIconSize
+                return (bubbleCount - 1) * getScaledIconSize()
                         + (bubbleCount - 2) * mExpandedBarIconsSpacing
                         + newBubbleScale * mExpandedBarIconsSpacing
                         + pivotAdjustment;
@@ -862,13 +922,16 @@
         if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
             return 0;
         }
+        float translationX;
         if (onLeft) {
             // Shift the first bubble only if there are more bubbles in addition to overflow
-            return bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
-                    ? mIconOverlapAmount : 0;
+            translationX = mBubbleBarPadding + (
+                    bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
+                            ? mIconOverlapAmount : 0);
         } else {
-            return bubbleIndex == 0 ? 0 : mIconOverlapAmount;
+            translationX = mBubbleBarPadding + (bubbleIndex == 0 ? 0 : mIconOverlapAmount);
         }
+        return translationX - getScaleIconShift();
     }
 
     /**
@@ -976,7 +1039,7 @@
         final int index = indexOfChild(mSelectedBubbleView);
         final float selectedBubbleTranslationX = getExpandedBubbleTranslationX(
                 index, getChildCount(), bubbleBarLocation.isOnLeft(isLayoutRtl()));
-        return getPaddingStart() + selectedBubbleTranslationX + mIconSize / 2f;
+        return selectedBubbleTranslationX + mIconSize / 2f;
     }
 
     private float arrowPositionForSelectedWhenCollapsed(BubbleBarLocation bubbleBarLocation) {
@@ -990,7 +1053,7 @@
             bubblePosition = index >= MAX_VISIBLE_BUBBLES_COLLAPSED
                     ? MAX_VISIBLE_BUBBLES_COLLAPSED - 1 : index;
         }
-        return getPaddingStart() + bubblePosition * (mIconOverlapAmount) + mIconSize / 2f;
+        return mBubbleBarPadding + bubblePosition * (mIconOverlapAmount) + getScaledIconSize() / 2f;
     }
 
     @Override
@@ -1038,7 +1101,6 @@
      */
     public float expandedWidth() {
         final int childCount = getChildCount();
-        final int horizontalPadding = getPaddingStart() + getPaddingEnd();
         // spaces amount is less than child count by 1, or 0 if no child views
         final float totalSpace;
         final float totalIconSize;
@@ -1047,22 +1109,22 @@
             // expanded, so we have at least 2 bubbles in the bubble bar.
             final float newBubbleScale = mNewBubbleScaleInAnimator.getAnimatedFraction();
             totalSpace = (childCount - 2 + newBubbleScale) * mExpandedBarIconsSpacing;
-            totalIconSize = (childCount - 1 + newBubbleScale) * mIconSize;
+            totalIconSize = (childCount - 1 + newBubbleScale) * getScaledIconSize();
         } else {
             totalSpace = Math.max(childCount - 1, 0) * mExpandedBarIconsSpacing;
-            totalIconSize = childCount * mIconSize;
+            totalIconSize = childCount * getScaledIconSize();
         }
-        return totalIconSize + totalSpace + horizontalPadding;
+        return totalIconSize + totalSpace + 2 * mBubbleBarPadding;
     }
 
     private float collapsedWidth() {
         final int childCount = getChildCount();
-        final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+        final float horizontalPadding = 2 * mBubbleBarPadding;
         // If there are more than 2 bubbles, the first 2 should be visible when collapsed.
         // Otherwise just the first bubble should be visible because we don't show the overflow.
         return childCount > MAX_VISIBLE_BUBBLES_COLLAPSED
-                ? mIconSize + mIconOverlapAmount + horizontalPadding
-                : mIconSize + horizontalPadding;
+                ? getScaledIconSize() + mIconOverlapAmount + horizontalPadding
+                : getScaledIconSize() + horizontalPadding;
     }
 
     private float getBubbleBarExpandedHeight() {
@@ -1071,7 +1133,7 @@
 
     float getBubbleBarCollapsedHeight() {
         // the pointer is invisible when collapsed
-        return mIconSize + mBubbleBarPadding * 2;
+        return getScaledIconSize() + mBubbleBarPadding * 2;
     }
 
     /**
@@ -1103,6 +1165,7 @@
         return mIsAnimatingNewBubble;
     }
 
+
     private boolean hasOverview() {
         // Overview is always the last bubble
         View lastChild = getChildAt(getChildCount() - 1);
@@ -1144,6 +1207,46 @@
         setContentDescription(contentDesc);
     }
 
+    private boolean isIconSizeOrPaddingUpdated(float newIconSize, float newBubbleBarPadding) {
+        return isIconSizeUpdated(newIconSize) || isPaddingUpdated(newBubbleBarPadding);
+    }
+
+    private boolean isIconSizeUpdated(float newIconSize) {
+        return Float.compare(mIconSize, newIconSize) != 0;
+    }
+
+    private boolean isPaddingUpdated(float newBubbleBarPadding) {
+        return Float.compare(mBubbleBarPadding, newBubbleBarPadding) != 0;
+    }
+
+    private void addAnimationCallBacks(@NonNull ValueAnimator animator,
+            @Nullable Runnable onStart,
+            @Nullable Runnable onEnd,
+            @Nullable ValueAnimator.AnimatorUpdateListener onUpdate) {
+        if (onUpdate != null) animator.addUpdateListener(onUpdate);
+        animator.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationCancel(Animator animator) {
+
+            }
+
+            @Override
+            public void onAnimationStart(Animator animator) {
+                if (onStart != null) onStart.run();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                if (onEnd != null) onEnd.run();
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animator) {
+
+            }
+        });
+    }
+
     /** Interface for BubbleBarView to communicate with its controller. */
     interface Controller {
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 0bfd1d7..eec095d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -108,9 +108,11 @@
         mBubbleDragController = bubbleControllers.bubbleDragController;
         mTaskbarStashController = controllers.taskbarStashController;
         mTaskbarInsetsController = controllers.taskbarInsetsController;
+        mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
 
-        mActivity.addOnDeviceProfileChangeListener(dp -> setBubbleBarIconSize(dp.taskbarIconSize));
-        setBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize);
+        mActivity.addOnDeviceProfileChangeListener(
+                dp -> updateBubbleBarIconSize(dp.taskbarIconSize, /* animate= */ true));
+        updateBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize, /* animate= */ false);
         mBubbleBarScale.updateValue(1f);
         mBubbleClickListener = v -> onBubbleClicked(v);
         mBubbleBarClickListener = v -> onBubbleBarClicked();
@@ -123,8 +125,6 @@
                         mBoundsChangeListener.onBoundsChanged();
                     }
                 });
-
-        mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
         mBarView.setController(new BubbleBarView.Controller() {
             @Override
             public float getBubbleBarTranslationY() {
@@ -299,33 +299,6 @@
         }
     }
 
-    private void setBubbleBarIconSize(int newIconSize) {
-        if (newIconSize == mIconSize) {
-            return;
-        }
-        Resources res = mActivity.getResources();
-        DisplayMetrics dm = res.getDisplayMetrics();
-        float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                APP_ICON_SMALL_DP, dm);
-        float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                APP_ICON_MEDIUM_DP, dm);
-        float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                APP_ICON_LARGE_DP, dm);
-        float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
-        float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
-        mIconSize = newIconSize <= smallMediumThreshold
-                ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
-                res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
-        float bubbleBarPadding = newIconSize >= mediumLargeThreshold
-                ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
-                res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
-
-        mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding);
-        mBarView.setPadding((int) bubbleBarPadding, mBarView.getPaddingTop(),
-                (int) bubbleBarPadding,
-                mBarView.getPaddingBottom());
-    }
-
     /** Sets a callback that updates the selected bubble after the bubble bar collapses. */
     public void setUpdateSelectedBubbleAfterCollapse(
             Consumer<String> updateSelectedBubbleAfterCollapse) {
@@ -360,6 +333,30 @@
     // Modifying view related properties.
     //
 
+    private void updateBubbleBarIconSize(int newIconSize, boolean animate) {
+        Resources res = mActivity.getResources();
+        DisplayMetrics dm = res.getDisplayMetrics();
+        float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                APP_ICON_SMALL_DP, dm);
+        float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                APP_ICON_MEDIUM_DP, dm);
+        float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                APP_ICON_LARGE_DP, dm);
+        float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
+        float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
+        mIconSize = newIconSize <= smallMediumThreshold
+                ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
+                res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+        float bubbleBarPadding = newIconSize >= mediumLargeThreshold
+                ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
+                res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+        if (animate) {
+            mBarView.animateBubbleBarIconSize(mIconSize, bubbleBarPadding);
+        } else {
+            mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding);
+        }
+    }
+
     /**
      * Sets the translation of the bubble bar during the swipe up gesture.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
new file mode 100644
index 0000000..3c4b63a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+/** Enums for all feature container that taskbar supports. */
+enum class TaskbarContainer {
+    ALL_APPS,
+    DIVIDER,
+    APP_ICONS,
+    RECENTS,
+    NAV_BUTTONS,
+    BUBBLES,
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
new file mode 100644
index 0000000..1ec075f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarControllers
+import com.android.launcher3.taskbar.TaskbarRecentAppsController
+import com.android.launcher3.util.DisplayController
+
+/** Evaluates all the features taskbar can have. */
+class TaskbarFeatureEvaluator(
+    private val taskbarActivityContext: TaskbarActivityContext,
+    private val taskbarControllers: TaskbarControllers,
+) {
+
+    val hasAllApps = true
+    val hasAppIcons = true
+    val hasBubbles = false
+    val hasNavButtons = taskbarActivityContext.isThreeButtonNav
+
+    val hasRecents: Boolean
+        get() = taskbarControllers.taskbarRecentAppsController.isEnabled
+
+    val hasDivider: Boolean
+        get() = enableTaskbarPinning() || hasRecents
+
+    val isTransient: Boolean
+        get() = DisplayController.isTransientTaskbar(taskbarActivityContext)
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
new file mode 100644
index 0000000..4cd895d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+/** Taskbar Icon Specs */
+object TaskbarIconSpecs {
+
+    val iconSize40dp = TaskbarIconSize(40)
+    val iconSize44dp = TaskbarIconSize(44)
+    val iconSize48dp = TaskbarIconSize(48)
+    val iconSize52dp = TaskbarIconSize(52)
+
+    val transientTaskbarIconSizes = arrayOf(iconSize44dp, iconSize48dp, iconSize52dp)
+
+    val defaultPersistentIconSize = iconSize40dp
+    val defaultTransientIconSize = iconSize44dp
+
+    // defined as row, columns
+    val transientTaskbarIconSizeByGridSize =
+        mapOf(
+            Pair(6, 5) to iconSize52dp,
+            Pair(4, 5) to iconSize48dp,
+            Pair(5, 4) to iconSize48dp,
+            Pair(4, 4) to iconSize48dp,
+            Pair(5, 6) to iconSize44dp,
+        )
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
new file mode 100644
index 0000000..02e5947
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+/** Evaluates the taskbar specs based on the taskbar grid size and the taskbar icon size. */
+class TaskbarSpecsEvaluator(private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator) {
+
+    fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
+        return if (taskbarFeatureEvaluator.isTransient) {
+            TaskbarIconSpecs.transientTaskbarIconSizeByGridSize.getOrDefault(
+                Pair(row, column),
+                TaskbarIconSpecs.defaultTransientIconSize,
+            )
+        } else {
+            TaskbarIconSpecs.defaultPersistentIconSize
+        }
+    }
+
+    fun getIconSizeStepDown(iconSize: TaskbarIconSize): TaskbarIconSize {
+        if (!taskbarFeatureEvaluator.isTransient) return TaskbarIconSpecs.defaultPersistentIconSize
+
+        val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
+        // return the current icon size if supplied icon size is unknown or we have reached the
+        // min icon size.
+        return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) iconSize
+        else TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
+    }
+
+    fun getIconSizeStepUp(iconSize: TaskbarIconSize): TaskbarIconSize {
+        if (!taskbarFeatureEvaluator.isTransient) return TaskbarIconSpecs.defaultPersistentIconSize
+
+        val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
+        // return the current icon size if supplied icon size is unknown or we have reached the
+        // max icon size.
+        return if (
+            currentIconSizeIndex == -1 ||
+                currentIconSizeIndex == TaskbarIconSpecs.transientTaskbarIconSizes.size - 1
+        ) {
+            iconSize
+        } else {
+            TaskbarIconSpecs.transientTaskbarIconSizes.get(currentIconSizeIndex + 1)
+        }
+    }
+}
+
+data class TaskbarIconSize(val size: Int)
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
index adbec65..7eb34a5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
@@ -133,16 +133,19 @@
      * <p>
      * This method should be called after an exit animation finishes, if applicable.
      */
-    @SuppressLint("WrongConstant")
     void maybeCloseWindow() {
-        if (mOverlayContext != null && (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)
-                || mOverlayContext.getDragController().isSystemDragInProgress())) {
-            return;
-        }
+        if (!canCloseWindow()) return;
         mProxyView.close(false);
         onDestroy();
     }
 
+    @SuppressLint("WrongConstant")
+    private boolean canCloseWindow() {
+        if (mOverlayContext == null) return true;
+        if (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)) return false;
+        return !mOverlayContext.getDragController().isSystemDragInProgress();
+    }
+
     /** Destroys the controller and any overlay window if present. */
     public void onDestroy() {
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
@@ -212,10 +215,17 @@
 
         @Override
         protected void handleClose(boolean animate) {
-            if (mIsOpen) {
-                mTaskbarContext.getDragLayer().removeView(this);
-                Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
-            }
+            if (!mIsOpen) return;
+            mTaskbarContext.getDragLayer().removeView(this);
+            Optional.ofNullable(mOverlayContext).ifPresent(c -> {
+                if (canCloseWindow()) {
+                    onDestroy(); // Window is already ready to be destroyed.
+                } else {
+                    // Close window's AFVs before destroying it. Its drag layer will attempt to
+                    // close the proxy view again once its children are removed.
+                    closeAllOpenViews(c, animate);
+                }
+            });
         }
 
         @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index e3a2bab..2168f7a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1118,7 +1118,7 @@
     }
 
     @Override
-    protected void collectStateHandlers(List<StateHandler> out) {
+    public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
         super.collectStateHandlers(out);
         out.add(getDepthController());
         out.add(new RecentsViewStateController(this));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 146ff3d..0469636 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -17,24 +17,32 @@
 
 import android.app.ActivityOptions
 import android.app.PendingIntent
+import android.app.role.RoleManager
 import android.content.Context
+import android.content.IIntentReceiver
+import android.content.IIntentSender
 import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.content.pm.LauncherActivityInfo
 import android.content.pm.LauncherApps
 import android.content.pm.ShortcutInfo
+import android.os.Bundle
 import android.os.Flags.allowPrivateProfile
+import android.os.IBinder
 import android.os.UserHandle
 import android.os.UserManager
 import android.util.ArrayMap
+import android.widget.Toast
 import android.window.RemoteTransition
 import com.android.launcher3.Flags.enablePrivateSpace
 import com.android.launcher3.Flags.enablePrivateSpaceInstallShortcut
 import com.android.launcher3.Flags.privateSpaceAppInstallerButton
 import com.android.launcher3.Flags.privateSpaceSysAppsSeparation
+import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.proxy.ProxyActivityStarter
 import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.Executors
 import com.android.launcher3.util.StartActivityParams
 import com.android.launcher3.util.UserIconInfo
 import com.android.quickstep.util.FadeOutRemoteTransition
@@ -115,8 +123,7 @@
                     intentSender =
                         mContext
                             .getSystemService(LauncherApps::class.java)
-                            ?.privateSpaceSettingsIntent
-                            ?: return null
+                            ?.privateSpaceSettingsIntent ?: return null
                     options =
                         ActivityOptions.makeBasic()
                             .setPendingIntentBackgroundActivityStartMode(
@@ -130,4 +137,50 @@
 
     override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
         lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
+
+    /**
+     * Starts an Activity which can be used to set this Launcher as the HOME app, via a consent
+     * screen. In case the consent screen cannot be shown, or the user does not set current Launcher
+     * as HOME app, a toast asking the user to do the latter is shown.
+     */
+    override fun assignDefaultHomeRole(context: Context) {
+        val roleManager = context.getSystemService(RoleManager::class.java)
+        if (
+            (roleManager!!.isRoleAvailable(RoleManager.ROLE_HOME) &&
+                !roleManager.isRoleHeld(RoleManager.ROLE_HOME))
+        ) {
+            val roleRequestIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME)
+            val pendingIntent =
+                PendingIntent(
+                    object : IIntentSender.Stub() {
+                        override fun send(
+                            code: Int,
+                            intent: Intent,
+                            resolvedType: String?,
+                            allowlistToken: IBinder?,
+                            finishedReceiver: IIntentReceiver?,
+                            requiredPermission: String?,
+                            options: Bundle?
+                        ) {
+                            if (code != -1) {
+                                Executors.MAIN_EXECUTOR.execute {
+                                    Toast.makeText(
+                                            context,
+                                            context.getString(
+                                                R.string.set_default_home_app,
+                                                context.getString(R.string.derived_app_name)
+                                            ),
+                                            Toast.LENGTH_LONG
+                                        )
+                                        .show()
+                                }
+                            }
+                        }
+                    }
+                )
+            val params = StartActivityParams(pendingIntent, 0)
+            params.intent = roleRequestIntent
+            context.startActivity(ProxyActivityStarter.getLaunchIntent(context, params))
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 9e896fd..89fbf4a 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -154,7 +154,8 @@
 
     @Override
     public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
-        final StateManager<RecentsState> stateManager = getCreatedContainer().getStateManager();
+        final StateManager<RecentsState, RecentsActivity> stateManager =
+                getCreatedContainer().getStateManager();
         if (stateManager.getState() == HOME) {
             exitRunnable.run();
             notifyRecentsOfOrientation(deviceState);
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 08c2e1c..c428827 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -36,6 +36,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Flags;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
@@ -242,7 +243,8 @@
 
     @Override
     public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
-        final StateManager<LauncherState> stateManager = getCreatedContainer().getStateManager();
+        final StateManager<LauncherState, Launcher> stateManager =
+                getCreatedContainer().getStateManager();
         stateManager.addStateListener(
                 new StateManager.StateListener<LauncherState>() {
                     @Override
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 26a7c2f..7da92bc 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -228,8 +228,9 @@
         }
         BaseActivityInterface<?, T> activityInterface =
                 mOverviewComponentObserver.getActivityInterface();
-        RecentsView visibleRecentsView = activityInterface.getVisibleRecentsView();
-        RecentsView createdRecentsView;
+
+        RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
+        RecentsView<?, ?> createdRecentsView;
 
         Log.d(TAG, "executeCommand: " + cmd
                 + " - visibleRecentsView: " + visibleRecentsView);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 3df62da..13e9844 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -115,7 +115,7 @@
     private TISBindHelper mTISBindHelper;
     private @Nullable FallbackTaskbarUIController mTaskbarUIController;
 
-    private StateManager<RecentsState> mStateManager;
+    private StateManager<RecentsState, RecentsActivity> mStateManager;
 
     // Strong refs to runners which are cleared when the activity is destroyed
     private RemoteAnimationFactory mActivityLaunchAnimationRunner;
@@ -386,6 +386,11 @@
         }
     }
 
+    @Override
+    public boolean shouldAnimateStateChange() {
+        return false;
+    }
+
     /**
      * Initialize/update the device profile.
      */
@@ -462,12 +467,12 @@
             };
 
     @Override
-    protected void collectStateHandlers(List<StateHandler> out) {
+    public void collectStateHandlers(List<StateHandler<RecentsState>> out) {
         out.add(new FallbackRecentsStateController(this));
     }
 
     @Override
-    public StateManager<RecentsState> getStateManager() {
+    public StateManager<RecentsState, RecentsActivity> getStateManager() {
         return mStateManager;
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 8bcdaa3..7adce74 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -45,6 +45,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED;
 
 import android.app.ActivityTaskManager;
 import android.content.Context;
@@ -386,7 +387,7 @@
         boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
                 || (mSystemUiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
                 || mRotationTouchHelper.isTaskListFrozen();
-        return canStartWithNavHidden && canStartTrackpadGesture();
+        return canStartWithNavHidden && canStartAnyGesture();
     }
 
     /**
@@ -395,14 +396,24 @@
      * mode.
      */
     public boolean canStartTrackpadGesture() {
-        return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_MAGNIFICATION_OVERLAP) == 0
-                && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
-                        || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0)
-                && (mSystemUiStateFlags & SYSUI_STATE_DEVICE_DREAMING) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) == 0;
+        boolean trackpadGesturesEnabled =
+                (mSystemUiStateFlags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) == 0;
+        return trackpadGesturesEnabled && canStartAnyGesture();
+    }
+
+    /**
+     * Common logic to determine if either trackpad or finger gesture can be started
+     */
+    private boolean canStartAnyGesture() {
+        boolean homeOrOverviewEnabled = (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+                || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
+        long gestureDisablingStates = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+                        | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+                        | SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+                        | SYSUI_STATE_MAGNIFICATION_OVERLAP
+                        | SYSUI_STATE_DEVICE_DREAMING
+                        | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
+        return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 485d6c4..f4a2738 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -93,7 +93,7 @@
     }
 
     @Override
-    public StateManager<RecentsState> getStateManager() {
+    public StateManager<RecentsState, RecentsActivity> getStateManager() {
         return mContainer.getStateManager();
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 4f1dbbe..36ea926 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -59,6 +59,7 @@
 
     @Nullable private TutorialType[] mTutorialSteps;
     private GestureSandboxFragment mCurrentFragment;
+    private GestureSandboxFragment mPendingFragment;
 
     private int mCurrentStep;
     private int mNumSteps;
@@ -176,16 +177,26 @@
                     && getResources().getConfiguration().orientation
                     == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 
-            showFragment(showRotationPrompt
+            GestureSandboxFragment fragment = showRotationPrompt
                     ? new RotationPromptFragment()
-                    : mCurrentFragment.canRecreateFragment()
-                            ? mCurrentFragment.recreateFragment() : mCurrentFragment);
+                    : mCurrentFragment.canRecreateFragment() || mPendingFragment == null
+                            ? mCurrentFragment.recreateFragment()
+                            : mPendingFragment.recreateFragment();
+            showFragment(fragment == null ? mCurrentFragment : fragment);
+
         } else {
             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         }
     }
 
     private void showFragment(@NonNull GestureSandboxFragment fragment) {
+        // Store the current fragment in mPendingFragment so that it can be recreated after the
+        // new fragment is shown.
+        if (mCurrentFragment.canRecreateFragment()) {
+            mPendingFragment = mCurrentFragment;
+        } else {
+            mPendingFragment = null;
+        }
         mCurrentFragment = fragment;
         getSupportFragmentManager().beginTransaction()
                 .replace(R.id.gesture_tutorial_fragment_container, mCurrentFragment)
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index c9647f5..c7c04ed 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.quickstep.DeviceConfigWrapper;
@@ -163,7 +164,8 @@
                     recentsOrientedState.getContainerInterface().getCreatedContainer();
             if (container != null) {
                 RecentsView recentsView = container.getOverviewPanel();
-                StateManager<LauncherState> stateManager = recentsView.getStateManager();
+                StateManager<LauncherState, StatefulActivity<LauncherState>> stateManager =
+                        recentsView.getStateManager();
                 if (stateManager.isInStableState(LauncherState.BACKGROUND_APP)
                         && stateManager.isInTransition()) {
 
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 132d1c1..492c801 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -62,7 +62,7 @@
      * {@link WorkspaceRevealAnim}.
      */
     public void animateWithVelocity(float velocity) {
-        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = mLauncher.getStateManager();
         LauncherState startState = stateManager.getState();
         if (startState != OVERVIEW) {
             Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState);
diff --git a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
index 3d9e09e..cbe0f19 100644
--- a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
@@ -27,6 +27,8 @@
     public int getPlaceholderIconFadeInEnd() { return 133; }
     public int getStagedRectSlideStart() { return 0; }
     public int getStagedRectSlideEnd() { return 333; }
+    public int getBackingScrimFadeInStart() { return 0; }
+    public int getBackingScrimFadeInEnd() { return 266; }
 
     public int getDuration() { return PHONE_CONFIRM_DURATION; }
 }
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 2a27dea..cfe5b2c 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
 import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
 
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
@@ -48,7 +49,7 @@
      * Sets up the initial onboarding behavior for the launcher
      */
     public static void setup(QuickstepLauncher launcher) {
-        StateManager<LauncherState> stateManager = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = launcher.getStateManager();
         if (!HOME_BOUNCE_SEEN.get(launcher)) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
                 @Override
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 8243ede..7ea04b1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -281,64 +281,45 @@
     }
 
     /**
-     * Creates and returns a view to fade in at .4 animation progress and adds it to the provided
-     * [pendingAnimation]. Assumes that animation will be the final split placeholder launch anim.
-     *
-     * [secondPlaceholderEndingBounds] refers to the second placeholder view that gets added on
-     * screen, not the logical second app. For landscape it's the left app and for portrait the top
-     * one.
+     * Creates and returns a fullscreen scrim to fade in behind the split confirm animation, and
+     * adds it to the provided [pendingAnimation].
      */
-    fun addDividerPlaceholderViewToAnim(
+    fun addScrimBehindAnim(
         pendingAnimation: PendingAnimation,
         container: RecentsViewContainer,
-        secondPlaceholderEndingBounds: Rect,
         context: Context
     ): View {
-        val mSplitDividerPlaceholderView = View(context)
+        val scrim = View(context)
         val recentsView = container.getOverviewPanel<RecentsView<*, *>>()
-        val dp: com.android.launcher3.DeviceProfile = container.getDeviceProfile()
+        val dp: DeviceProfile = container.getDeviceProfile()
         // Add it before/under the most recently added first floating taskView
         val firstAddedSplitViewIndex: Int =
             container
                 .getDragLayer()
                 .indexOfChild(recentsView.splitSelectController.firstFloatingTaskView)
-        container.getDragLayer().addView(mSplitDividerPlaceholderView, firstAddedSplitViewIndex)
-        val lp = mSplitDividerPlaceholderView.layoutParams as InsettableFrameLayout.LayoutParams
+        container.getDragLayer().addView(scrim, firstAddedSplitViewIndex)
+        // Make the scrim fullscreen
+        val lp = scrim.layoutParams as InsettableFrameLayout.LayoutParams
         lp.topMargin = 0
+        lp.height = dp.heightPx
+        lp.width = dp.widthPx
 
-        if (dp.isLeftRightSplit) {
-            lp.height = secondPlaceholderEndingBounds.height()
-            lp.width =
-                container
-                    .asContext()
-                    .resources
-                    .getDimensionPixelSize(R.dimen.split_divider_handle_region_height)
-            mSplitDividerPlaceholderView.translationX =
-                secondPlaceholderEndingBounds.right - lp.width / 2f
-            mSplitDividerPlaceholderView.translationY = 0f
-        } else {
-            lp.height =
-                container
-                    .asContext()
-                    .resources
-                    .getDimensionPixelSize(R.dimen.split_divider_handle_region_height)
-            lp.width = secondPlaceholderEndingBounds.width()
-            mSplitDividerPlaceholderView.translationY =
-                secondPlaceholderEndingBounds.top - lp.height / 2f
-            mSplitDividerPlaceholderView.translationX = 0f
-        }
-
-        mSplitDividerPlaceholderView.alpha = 0f
-        mSplitDividerPlaceholderView.setBackgroundColor(
+        scrim.alpha = 0f
+        scrim.setBackgroundColor(
             container.asContext().resources.getColor(R.color.taskbar_background_dark)
         )
-        val timings = AnimUtils.getDeviceSplitToConfirmTimings(dp.isTablet)
+        val timings = AnimUtils.getDeviceSplitToConfirmTimings(dp.isTablet) as SplitToConfirmTimings
         pendingAnimation.setViewAlpha(
-            mSplitDividerPlaceholderView,
+            scrim,
             1f,
-            Interpolators.clampToProgress(timings.stagedRectScaleXInterpolator, 0.4f, 1f)
+            Interpolators.clampToProgress(
+                timings.backingScrimFadeInterpolator,
+                timings.backingScrimFadeInStartOffset,
+                timings.backingScrimFadeInEndOffset
+            )
         )
-        return mSplitDividerPlaceholderView
+
+        return scrim
     }
 
     /** Does not play any animation if user is not currently in split selection state. */
@@ -510,7 +491,7 @@
         apps: Array<RemoteAnimationTarget>?,
         wallpapers: Array<RemoteAnimationTarget>?,
         nonApps: Array<RemoteAnimationTarget>?,
-        stateManager: StateManager<*>,
+        stateManager: StateManager<*, *>,
         depthController: DepthController?,
         info: TransitionInfo?,
         t: Transaction?,
@@ -589,7 +570,7 @@
     @VisibleForTesting
     fun composeRecentsSplitLaunchAnimator(
         launchingTaskView: GroupedTaskView,
-        stateManager: StateManager<*>,
+        stateManager: StateManager<*, *>,
         depthController: DepthController?,
         info: TransitionInfo,
         t: Transaction,
@@ -617,7 +598,7 @@
         apps: Array<RemoteAnimationTarget>,
         wallpapers: Array<RemoteAnimationTarget>,
         nonApps: Array<RemoteAnimationTarget>,
-        stateManager: StateManager<*>,
+        stateManager: StateManager<*, *>,
         depthController: DepthController?,
         finishCallback: Runnable
     ) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
index d1ec2b6..7557ad1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -17,6 +17,7 @@
 package com.android.quickstep.util;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.LINEAR;
 
 import android.view.animation.Interpolator;
 
@@ -31,6 +32,8 @@
     abstract public int getPlaceholderIconFadeInEnd();
     abstract public int getStagedRectSlideStart();
     abstract public int getStagedRectSlideEnd();
+    abstract public int getBackingScrimFadeInStart();
+    abstract public int getBackingScrimFadeInEnd();
 
     // Common timings
     public int getInstructionsFadeStart() { return 0; }
@@ -39,6 +42,7 @@
     public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
     public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
     public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
+    public Interpolator getBackingScrimFadeInterpolator() { return LINEAR; }
 
     abstract public int getDuration();
 
@@ -48,4 +52,10 @@
     public float getInstructionsFadeEndOffset() {
         return (float) getInstructionsFadeEnd() / getDuration();
     }
+    public float getBackingScrimFadeInStartOffset() {
+        return (float) getBackingScrimFadeInStart() / getDuration();
+    }
+    public float getBackingScrimFadeInEndOffset() {
+        return (float) getBackingScrimFadeInEnd() / getDuration();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 2396512..4962367 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -163,9 +163,8 @@
                 new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                 false /* fadeWithThumbnail */, true /* isStagedTask */);
 
-        View mSplitDividerPlaceholderView = recentsView.getSplitSelectController()
-                .getSplitAnimationController().addDividerPlaceholderViewToAnim(pendingAnimation,
-                        mLauncher, secondTaskEndingBounds, view.getContext());
+        View backingScrim = recentsView.getSplitSelectController().getSplitAnimationController()
+                .addScrimBehindAnim(pendingAnimation, mLauncher, view.getContext());
 
         FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher,
                 view, bitmap, icon, secondTaskStartingBounds);
@@ -197,7 +196,7 @@
             private void cleanUp() {
                 mLauncher.getDragLayer().removeView(firstFloatingTaskView);
                 mLauncher.getDragLayer().removeView(secondFloatingTaskView);
-                mLauncher.getDragLayer().removeView(mSplitDividerPlaceholderView);
+                mLauncher.getDragLayer().removeView(backingScrim);
                 mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher);
                 mController.resetState();
             }
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 2d606f3..e44f148 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -50,6 +50,8 @@
 
     private static final float END_PROGRESS = 1.0f;
 
+    private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f;
+
     private final int mTaskId;
     private final ActivityInfo mActivityInfo;
     private final SurfaceControl mLeash;
@@ -135,6 +137,7 @@
         mDestinationBoundsTransformed.set(destinationBoundsTransformed);
         mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius, shadowRadius);
 
+        final float aspectRatio = destinationBounds.width() / (float) destinationBounds.height();
         String reasonForCreateOverlay = null; // For debugging purpose.
         if (sourceRectHint.isEmpty()) {
             reasonForCreateOverlay = "Source rect hint is empty";
@@ -149,15 +152,20 @@
         } else if (!appBounds.contains(sourceRectHint)) {
             // This is a situation in which the source hint rect is outside the app bounds, so it is
             // not a valid rectangle to use for cropping app surface
-            sourceRectHint.setEmpty();
             reasonForCreateOverlay = "Source rect hint exceeds display bounds " + sourceRectHint;
+            sourceRectHint.setEmpty();
+        } else if (Math.abs(
+                aspectRatio - (sourceRectHint.width() / (float) sourceRectHint.height()))
+                > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+            // The source rect hint does not aspect ratio
+            reasonForCreateOverlay = "Source rect hint does not match aspect ratio "
+                    + sourceRectHint + " aspect ratio " + aspectRatio;
+            sourceRectHint.setEmpty();
         }
 
         if (sourceRectHint.isEmpty()) {
             // Crop a Rect matches the aspect ratio and pivots at the center point.
             // To make the animation path simplified.
-            final float aspectRatio = destinationBounds.width()
-                    / (float) destinationBounds.height();
             if ((appBounds.width() / (float) appBounds.height()) > aspectRatio) {
                 // use the full height.
                 mSourceRectHint.set(0, 0,
diff --git a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
index 3756b4a..d74473c 100644
--- a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
@@ -27,6 +27,8 @@
     public int getPlaceholderIconFadeInEnd() { return 250; }
     public int getStagedRectSlideStart() { return 0; }
     public int getStagedRectSlideEnd() { return 500; }
+    public int getBackingScrimFadeInStart() { return 0; }
+    public int getBackingScrimFadeInEnd() { return 400; }
 
     public int getDuration() { return TABLET_CONFIRM_DURATION; }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 5284b44..e48a7c6 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -37,6 +37,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
@@ -104,7 +105,7 @@
     }
 
     @Override
-    public StateManager<LauncherState> getStateManager() {
+    public StateManager<LauncherState, Launcher> getStateManager() {
         return mContainer.getStateManager();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9096b75..4f802c9 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -152,6 +152,7 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.OverScroll;
@@ -738,7 +739,11 @@
     private int mSplitHiddenTaskViewIndex = -1;
     @Nullable
     private FloatingTaskView mSecondFloatingTaskView;
-    private View mSplitDividerPlaceholderView;
+    /**
+     * A fullscreen scrim that goes behind the splitscreen animation to hide color conflicts and
+     * possible flickers. Removed after tasks + divider finish animating in.
+     */
+    private View mSplitScrim;
 
     /**
      * The task to be removed and immediately re-added. Should not be added to task pool.
@@ -2487,7 +2492,9 @@
     /** Returns whether user can start home based on state in {@link OverviewCommandHelper}. */
     protected abstract boolean canStartHomeSafely();
 
-    public abstract StateManager<STATE_TYPE> getStateManager();
+    /** Returns the state manager used in RecentsView **/
+    public abstract StateManager<STATE_TYPE,
+            ? extends StatefulContainer<STATE_TYPE>> getStateManager();
 
     public void reset() {
         setCurrentTask(-1);
@@ -4926,9 +4933,8 @@
                 mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
                 secondTaskEndingBounds);
 
-        mSplitDividerPlaceholderView = mSplitSelectStateController
-                .getSplitAnimationController().addDividerPlaceholderViewToAnim(pendingAnimation,
-                        mContainer, secondTaskEndingBounds, getContext());
+        mSplitScrim = mSplitSelectStateController.getSplitAnimationController()
+                .addScrimBehindAnim(pendingAnimation, mContainer, getContext());
         FloatingTaskView firstFloatingTaskView =
                 mSplitSelectStateController.getFirstFloatingTaskView();
         firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
@@ -4983,7 +4989,7 @@
             safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
             safeRemoveDragLayerView(mSecondFloatingTaskView);
             safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
-            safeRemoveDragLayerView(mSplitDividerPlaceholderView);
+            safeRemoveDragLayerView(mSplitScrim);
             mSecondFloatingTaskView = null;
             mSplitSelectSource = null;
             mSplitSelectStateController.getSplitAnimationController()
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 447002f..4283d0e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -61,6 +61,8 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 
+import java.util.Objects;
+
 /**
  * A task in the Recents view.
  *
@@ -222,14 +224,16 @@
     public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData,
             boolean refreshNow) {
         mTask = task;
-        boolean thumbnailWasNull = mThumbnailData == null;
+        ThumbnailData oldThumbnailData = mThumbnailData;
         mThumbnailData = (thumbnailData != null && thumbnailData.getThumbnail() != null)
                 ? thumbnailData : null;
         if (mTask != null) {
             updateSplashView(mTask.icon);
         }
         if (refreshNow) {
-            refresh(thumbnailWasNull && mThumbnailData != null);
+            Long oldSnapshotId = oldThumbnailData != null ? oldThumbnailData.getSnapshotId() : null;
+            Long snapshotId = mThumbnailData != null ? mThumbnailData.getSnapshotId() : null;
+            refresh(snapshotId != null && !Objects.equals(oldSnapshotId, snapshotId));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 5e79743..9c1aaa6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -63,6 +63,7 @@
 import com.android.launcher3.testing.TestLogging
 import com.android.launcher3.testing.shared.TestProtocol
 import com.android.launcher3.util.CancellableTask
+import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.MultiPropertyFactory
 import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
@@ -76,6 +77,7 @@
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.launcher3.util.ViewPool
 import com.android.launcher3.util.rects.set
+import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.RemoteAnimationTargets
 import com.android.quickstep.TaskAnimationManager
@@ -1574,7 +1576,20 @@
 
         @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
         open fun computeWindowCornerRadius(context: Context): Float {
-            return QuickStepContract.getWindowCornerRadius(context)
+            val activityContext: ActivityContext? = ActivityContext.lookupContextNoThrow(context)
+
+            // The corner radius is fixed to match when Taskbar is persistent mode
+            return if (
+                activityContext != null &&
+                    activityContext.deviceProfile?.isTaskbarPresent == true &&
+                    DisplayController.isTransientTaskbar(context)
+            ) {
+                context.resources
+                    .getDimensionPixelSize(R.dimen.persistent_taskbar_corner_radius)
+                    .toFloat()
+            } else {
+                QuickStepContract.getWindowCornerRadius(context)
+            }
         }
 
         /** Sets the progress in range [0, 1] */
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
new file mode 100644
index 0000000..a999e7f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 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.taskbar
+
+import android.app.Instrumentation
+import android.app.PendingIntent
+import android.content.IIntentSender
+import android.content.Intent
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ServiceTestRule
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
+import com.android.quickstep.AllAppsActionManager
+import com.android.quickstep.TouchInteractionService
+import com.android.quickstep.TouchInteractionService.TISBinder
+import org.junit.Assume.assumeTrue
+import org.junit.rules.MethodRule
+import org.junit.runners.model.FrameworkMethod
+import org.junit.runners.model.Statement
+
+/**
+ * Manages the Taskbar lifecycle for unit tests.
+ *
+ * See [InjectController] for grabbing controller(s) under test with minimal boilerplate.
+ *
+ * The rule interacts with [TaskbarManager] on the main thread. A good rule of thumb for tests is
+ * that code that is executed on the main thread in production should also happen on that thread
+ * when tested.
+ *
+ * `@UiThreadTest` is a simple way to run an entire test body on the main thread. But if a test
+ * executes code that appends message(s) to the main thread's `MessageQueue`, the annotation will
+ * prevent those messages from being processed until after the test body finishes.
+ *
+ * To test pending messages, instead use something like [Instrumentation.runOnMainSync] to perform
+ * only sections of the test body on the main thread synchronously:
+ * ```
+ * @Test
+ * fun example() {
+ *     instrumentation.runOnMainSync { doWorkThatPostsMessage() }
+ *     // Second lambda will not execute until message is processed.
+ *     instrumentation.runOnMainSync { verifyMessageResults() }
+ * }
+ * ```
+ */
+class TaskbarUnitTestRule : MethodRule {
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val serviceTestRule = ServiceTestRule()
+
+    private lateinit var taskbarManager: TaskbarManager
+    private lateinit var target: Any
+
+    val activityContext: TaskbarActivityContext
+        get() {
+            return taskbarManager.currentActivityContext
+                ?: throw RuntimeException("Failed to obtain TaskbarActivityContext.")
+        }
+
+    override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+                this@TaskbarUnitTestRule.target = target
+
+                val context = instrumentation.targetContext
+                instrumentation.runOnMainSync {
+                    assumeTrue(
+                        LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent
+                    )
+                }
+
+                // Check for existing Taskbar instance from Launcher process.
+                val launcherTaskbarManager: TaskbarManager? =
+                    if (!isRunningInRobolectric) {
+                        try {
+                            val tisBinder =
+                                serviceTestRule.bindService(
+                                    Intent(context, TouchInteractionService::class.java)
+                                ) as? TISBinder
+                            tisBinder?.taskbarManager
+                        } catch (_: Exception) {
+                            null
+                        }
+                    } else {
+                        null
+                    }
+
+                instrumentation.runOnMainSync {
+                    taskbarManager =
+                        TaskbarManager(
+                            context,
+                            AllAppsActionManager(context, UI_HELPER_EXECUTOR) {
+                                PendingIntent(IIntentSender.Default())
+                            },
+                            object : TaskbarNavButtonCallbacks {},
+                        )
+                }
+
+                try {
+                    // Replace Launcher Taskbar window with test instance.
+                    instrumentation.runOnMainSync {
+                        launcherTaskbarManager?.removeTaskbarRootViewFromWindow()
+                        taskbarManager.onUserUnlocked() // Required to complete initialization.
+                    }
+
+                    injectControllers()
+                    base.evaluate()
+                } finally {
+                    // Revert Taskbar window.
+                    instrumentation.runOnMainSync {
+                        taskbarManager.destroy()
+                        launcherTaskbarManager?.addTaskbarRootViewToWindow()
+                    }
+                }
+            }
+        }
+    }
+
+    /** Simulates Taskbar recreation lifecycle. */
+    fun recreateTaskbar() {
+        taskbarManager.recreateTaskbar()
+        injectControllers()
+    }
+
+    private fun injectControllers() {
+        val controllers = activityContext.controllers
+        val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type }
+        target.javaClass.fields
+            .filter { it.isAnnotationPresent(InjectController::class.java) }
+            .forEach {
+                it.set(
+                    target,
+                    controllerFieldsByType[it.type]?.get(controllers)
+                        ?: throw NoSuchElementException("Failed to find controller for ${it.type}"),
+                )
+            }
+    }
+
+    /**
+     * Annotates test controller fields to inject the corresponding controllers from the current
+     * [TaskbarControllers] instance.
+     *
+     * Controllers are injected during test setup and upon calling [recreateTaskbar].
+     *
+     * Multiple controllers can be injected if needed.
+     */
+    @Retention(AnnotationRetention.RUNTIME)
+    @Target(AnnotationTarget.FIELD)
+    annotation class InjectController
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
new file mode 100644
index 0000000..c09dcf2
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2024 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.taskbar.allapps
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Process
+import androidx.test.annotation.UiThreadTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.appprediction.PredictionRowView
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.notification.NotificationKeyData
+import com.android.launcher3.taskbar.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.util.PackageUserKey
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+class TaskbarAllAppsControllerTest {
+
+    @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
+
+    @InjectController lateinit var allAppsController: TaskbarAllAppsController
+    @InjectController lateinit var overlayController: TaskbarOverlayController
+
+    @Test
+    @UiThreadTest
+    fun testToggle_once_showsAllApps() {
+        allAppsController.toggle()
+        assertThat(allAppsController.isOpen).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testToggle_twice_closesAllApps() {
+        allAppsController.toggle()
+        allAppsController.toggle()
+        assertThat(allAppsController.isOpen).isFalse()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testToggle_taskbarRecreated_allAppsReopened() {
+        allAppsController.toggle()
+        taskbarUnitTestRule.recreateTaskbar()
+        assertThat(allAppsController.isOpen).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetApps_beforeOpened_cachesInfo() {
+        allAppsController.setApps(TEST_APPS, 0, emptyMap())
+        allAppsController.toggle()
+
+        val overlayContext = overlayController.requestWindow()
+        assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetApps_afterOpened_updatesStore() {
+        allAppsController.toggle()
+        allAppsController.setApps(TEST_APPS, 0, emptyMap())
+
+        val overlayContext = overlayController.requestWindow()
+        assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetPredictedApps_beforeOpened_cachesInfo() {
+        allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+        allAppsController.toggle()
+
+        val predictedApps =
+            overlayController
+                .requestWindow()
+                .appsView
+                .floatingHeaderView
+                .findFixedRowByType(PredictionRowView::class.java)
+                .predictedApps
+        assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetPredictedApps_afterOpened_cachesInfo() {
+        allAppsController.toggle()
+        allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+
+        val predictedApps =
+            overlayController
+                .requestWindow()
+                .appsView
+                .floatingHeaderView
+                .findFixedRowByType(PredictionRowView::class.java)
+                .predictedApps
+        assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS)
+    }
+
+    @Test
+    fun testUpdateNotificationDots_appInfo_hasDot() {
+        getInstrumentation().runOnMainSync {
+            allAppsController.setApps(TEST_APPS, 0, emptyMap())
+            allAppsController.toggle()
+            taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
+                PackageUserKey.fromItemInfo(TEST_APPS[0]),
+                NotificationKeyData("key"),
+            )
+        }
+
+        // Ensure the recycler view fully inflates before trying to grab an icon.
+        getInstrumentation().runOnMainSync {
+            val btv =
+                overlayController
+                    .requestWindow()
+                    .appsView
+                    .activeRecyclerView
+                    .findViewHolderForAdapterPosition(0)
+                    ?.itemView as? BubbleTextView
+            assertThat(btv?.hasDot()).isTrue()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testUpdateNotificationDots_predictedApp_hasDot() {
+        allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+        allAppsController.toggle()
+
+        taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
+            PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]),
+            NotificationKeyData("key"),
+        )
+
+        val predictionRowView =
+            overlayController
+                .requestWindow()
+                .appsView
+                .floatingHeaderView
+                .findFixedRowByType(PredictionRowView::class.java)
+        val btv = predictionRowView.getChildAt(0) as BubbleTextView
+        assertThat(btv.hasDot()).isTrue()
+    }
+
+    private companion object {
+        private val TEST_APPS =
+            Array(16) {
+                AppInfo(
+                    ComponentName(
+                        getInstrumentation().context,
+                        "com.android.launcher3.tests.Activity$it",
+                    ),
+                    "Test App $it",
+                    Process.myUserHandle(),
+                    Intent(),
+                )
+            }
+
+        private val TEST_PREDICTED_APPS = TEST_APPS.take(4).map { WorkspaceItemInfo(it) }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
new file mode 100644
index 0000000..eebd8f9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2024 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.taskbar.overlay
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.view.MotionEvent
+import androidx.test.annotation.UiThreadTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP
+import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
+import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY
+import com.android.launcher3.AbstractFloatingView.hasOpenView
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.views.BaseDragLayer
+import com.android.systemui.shared.system.TaskStackChangeListeners
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023"])
+class TaskbarOverlayControllerTest {
+
+    @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
+    @InjectController lateinit var overlayController: TaskbarOverlayController
+
+    private val taskbarContext: TaskbarActivityContext
+        get() = taskbarUnitTestRule.activityContext
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_twice_reusesWindow() {
+        val context1 = overlayController.requestWindow()
+        val context2 = overlayController.requestWindow()
+        assertThat(context1).isSameInstanceAs(context2)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() {
+        val context1 = overlayController.requestWindow()
+        overlayController.hideWindow()
+
+        val context2 = overlayController.requestWindow()
+        assertThat(context1).isNotSameInstanceAs(context2)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_afterHidingOverlay_createsNewWindow() {
+        val context1 = overlayController.requestWindow()
+        TestOverlayView.show(context1)
+        overlayController.hideWindow()
+
+        val context2 = overlayController.requestWindow()
+        assertThat(context1).isNotSameInstanceAs(context2)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_addsProxyView() {
+        TestOverlayView.show(overlayController.requestWindow())
+        assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_closeProxyView_closesOverlay() {
+        val overlay = TestOverlayView.show(overlayController.requestWindow())
+        AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)
+        assertThat(overlay.isOpen).isFalse()
+    }
+
+    @Test
+    fun testRequestWindow_attachesDragLayer() {
+        lateinit var dragLayer: BaseDragLayer<*>
+        getInstrumentation().runOnMainSync {
+            dragLayer = overlayController.requestWindow().dragLayer
+        }
+
+        // Allow drag layer to attach before checking.
+        getInstrumentation().runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testHideWindow_closesOverlay() {
+        val overlay = TestOverlayView.show(overlayController.requestWindow())
+        overlayController.hideWindow()
+        assertThat(overlay.isOpen).isFalse()
+    }
+
+    @Test
+    fun testHideWindow_detachesDragLayer() {
+        lateinit var dragLayer: BaseDragLayer<*>
+        getInstrumentation().runOnMainSync {
+            dragLayer = overlayController.requestWindow().dragLayer
+        }
+
+        // Wait for drag layer to be attached to window before hiding.
+        getInstrumentation().runOnMainSync {
+            overlayController.hideWindow()
+            assertThat(dragLayer.isAttachedToWindow).isFalse()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testTwoOverlays_closeOne_windowStaysOpen() {
+        val context = overlayController.requestWindow()
+        val overlay1 = TestOverlayView.show(context)
+        val overlay2 = TestOverlayView.show(context)
+
+        overlay1.close(false)
+        assertThat(overlay2.isOpen).isTrue()
+        assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testTwoOverlays_closeAll_closesWindow() {
+        val context = overlayController.requestWindow()
+        val overlay1 = TestOverlayView.show(context)
+        val overlay2 = TestOverlayView.show(context)
+
+        overlay1.close(false)
+        overlay2.close(false)
+        assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRecreateTaskbar_closesWindow() {
+        TestOverlayView.show(overlayController.requestWindow())
+        taskbarUnitTestRule.recreateTaskbar()
+        assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
+    }
+
+    @Test
+    fun testTaskMovedToFront_closesOverlay() {
+        lateinit var overlay: TestOverlayView
+        getInstrumentation().runOnMainSync {
+            overlay = TestOverlayView.show(overlayController.requestWindow())
+        }
+
+        TaskStackChangeListeners.getInstance().listenerImpl.onTaskMovedToFront(RunningTaskInfo())
+        // Make sure TaskStackChangeListeners' Handler posts the callback before checking state.
+        getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
+    }
+
+    @Test
+    fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() {
+        lateinit var overlay: TestOverlayView
+        getInstrumentation().runOnMainSync {
+            overlay = TestOverlayView.show(overlayController.requestWindow())
+            taskbarContext.controllers.sharedState?.allAppsVisible = false
+        }
+
+        TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
+        getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() }
+    }
+
+    @Test
+    fun testTaskStackChanged_allAppsOpen_closesOverlay() {
+        lateinit var overlay: TestOverlayView
+        getInstrumentation().runOnMainSync {
+            overlay = TestOverlayView.show(overlayController.requestWindow())
+            taskbarContext.controllers.sharedState?.allAppsVisible = true
+        }
+
+        TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
+        getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() {
+        val overlayContext = overlayController.requestWindow()
+        val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP }
+
+        overlayController.updateLauncherDeviceProfile(
+            overlayController.launcherDeviceProfile
+                .toBuilder(overlayContext)
+                .setGestureMode(false)
+                .build()
+        )
+
+        assertThat(overlay.isOpen).isFalse()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testUpdateLauncherDeviceProfile_overlayRebindSafe_overlayStaysOpen() {
+        val overlayContext = overlayController.requestWindow()
+        val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_TASKBAR_ALL_APPS }
+
+        overlayController.updateLauncherDeviceProfile(
+            overlayController.launcherDeviceProfile
+                .toBuilder(overlayContext)
+                .setGestureMode(false)
+                .build()
+        )
+
+        assertThat(overlay.isOpen).isTrue()
+    }
+
+    private class TestOverlayView
+    private constructor(
+        private val overlayContext: TaskbarOverlayContext,
+    ) : AbstractFloatingView(overlayContext, null) {
+
+        var type = TYPE_OPTIONS_POPUP
+
+        private fun show() {
+            mIsOpen = true
+            overlayContext.dragLayer.addView(this)
+        }
+
+        override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean = false
+
+        override fun handleClose(animate: Boolean) = overlayContext.dragLayer.removeView(this)
+
+        override fun isOfType(type: Int): Boolean = (type and this.type) != 0
+
+        companion object {
+            /** Adds a generic View to the Overlay window for testing. */
+            fun show(context: TaskbarOverlayContext): TestOverlayView {
+                return TestOverlayView(context).apply { show() }
+            }
+        }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 250dc7b..d40f8ab 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -78,7 +78,7 @@
     private val mockSplitSourceDrawable: Drawable = mock()
     private val mockSplitSourceView: View = mock()
 
-    private val stateManager: StateManager<*> = mock()
+    private val stateManager: StateManager<*, *> = mock()
     private val depthController: DepthController = mock()
     private val transitionInfo: TransitionInfo = mock()
     private val transaction: Transaction = mock()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index aa08ca4..bab84ef 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -31,6 +31,7 @@
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.statehandlers.DepthController
 import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.statemanager.StatefulActivity
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.quickstep.RecentsModel
@@ -61,7 +62,7 @@
     private val depthController: DepthController = mock()
     private val statsLogManager: StatsLogManager = mock()
     private val statsLogger: StatsLogger = mock()
-    private val stateManager: StateManager<LauncherState> = mock()
+    private val stateManager: StateManager<LauncherState, StatefulActivity<LauncherState>> = mock()
     private val handler: Handler = mock()
     private val context: RecentsViewContainer = mock()
     private val recentsModel: RecentsModel = mock()
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index f3115c6..04012c0 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -37,7 +37,7 @@
     lateinit var stateListener: StateManager.StateListener<RecentsState>
 
     private val recentsActivity: RecentsActivity = mock()
-    private val stateManager: StateManager<RecentsState> = mock()
+    private val stateManager: StateManager<RecentsState, RecentsActivity> = mock()
 
     @Before
     override fun setup() {
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 5157c71..80fbce7 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -9,8 +9,19 @@
 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
 import com.android.launcher3.util.DisplayController.Info
 import com.android.launcher3.util.NavigationMode
-import com.android.launcher3.util.window.WindowManagerProxy
 import com.android.quickstep.util.GestureExclusionManager
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -28,7 +39,6 @@
 class RecentsAnimationDeviceStateTest {
 
     @Mock private lateinit var exclusionManager: GestureExclusionManager
-    @Mock private lateinit var windowManagerProxy: WindowManagerProxy
     @Mock private lateinit var info: Info
 
     private val context = ApplicationProvider.getApplicationContext() as Context
@@ -108,4 +118,88 @@
 
         verifyZeroInteractions(exclusionManager)
     }
+
+    @Test
+    fun trackpadGesturesNotAllowedForSelectedStates() {
+        val disablingStates = GESTURE_DISABLING_SYSUI_STATES +
+                SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+
+        allSysUiStates().forEach { state ->
+            val canStartGesture = !disablingStates.contains(state)
+            underTest.setSystemUiFlags(state)
+            assertThat(underTest.canStartTrackpadGesture()).isEqualTo(canStartGesture)
+        }
+    }
+
+    @Test
+    fun trackpadGesturesNotAllowedIfHomeAndOverviewIsDisabled() {
+        val stateToExpectedResult = mapOf(
+            SYSUI_STATE_HOME_DISABLED to true,
+            SYSUI_STATE_OVERVIEW_DISABLED to true,
+            DEFAULT_STATE
+                .enable(SYSUI_STATE_OVERVIEW_DISABLED)
+                .enable(SYSUI_STATE_HOME_DISABLED) to false
+        )
+
+        stateToExpectedResult.forEach { (state, allowed) ->
+            underTest.setSystemUiFlags(state)
+            assertThat(underTest.canStartTrackpadGesture()).isEqualTo(allowed)
+        }
+    }
+
+    @Test
+    fun systemGesturesNotAllowedForSelectedStates() {
+        val disablingStates = GESTURE_DISABLING_SYSUI_STATES + SYSUI_STATE_NAV_BAR_HIDDEN
+
+        allSysUiStates().forEach { state ->
+            val canStartGesture = !disablingStates.contains(state)
+            underTest.setSystemUiFlags(state)
+            assertThat(underTest.canStartSystemGesture()).isEqualTo(canStartGesture)
+        }
+    }
+
+    @Test
+    fun systemGesturesNotAllowedWhenGestureStateDisabledAndNavBarVisible() {
+        val stateToExpectedResult = mapOf(
+            DEFAULT_STATE
+                .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+                .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+            DEFAULT_STATE
+                .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+                .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+            DEFAULT_STATE
+                .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+                .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+            DEFAULT_STATE
+                .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+                .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to false,
+        )
+
+        stateToExpectedResult.forEach {(state, gestureAllowed) ->
+            underTest.setSystemUiFlags(state)
+            assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
+        }
+    }
+
+    private fun allSysUiStates(): List<Long> {
+        // SYSUI_STATES_* are binary flags
+        return (0..SYSUI_STATES_COUNT).map { 1L shl it }
+    }
+
+    companion object {
+        private val GESTURE_DISABLING_SYSUI_STATES = listOf(
+            SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+            SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
+            SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
+            SYSUI_STATE_MAGNIFICATION_OVERLAP,
+            SYSUI_STATE_DEVICE_DREAMING,
+            SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
+        )
+        private const val SYSUI_STATES_COUNT = 33
+        private const val DEFAULT_STATE = 0L
+    }
+
+    private fun Long.enable(state: Long) = this or state
+
+    private fun Long.disable(state: Long) = this and state.inv()
 }
diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt b/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
new file mode 100644
index 0000000..b637e7d
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.taskbar.customization
+
+import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator
+import com.android.launcher3.taskbar.customization.TaskbarIconSpecs
+import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@RunWith(LauncherMultivalentJUnit::class)
+class TaskbarSpecsEvaluatorTest {
+
+    private val taskbarFeatureEvaluator = mock<TaskbarFeatureEvaluator>()
+    private val taskbarSpecsEvaluator = spy(TaskbarSpecsEvaluator(taskbarFeatureEvaluator))
+
+    @Test
+    fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumn() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(6, 5))
+            .isEqualTo(TaskbarIconSpecs.iconSize52dp)
+    }
+
+    @Test
+    fun testGetIconSizeByGrid_whenTaskbarIsTransient_withInvalidRowAndColumn() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(1, 2))
+            .isEqualTo(TaskbarIconSpecs.defaultTransientIconSize)
+    }
+
+    @Test
+    fun testGetIconSizeByGrid_whenTaskbarIsPersistent() {
+        doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(6, 5))
+            .isEqualTo(TaskbarIconSpecs.defaultPersistentIconSize)
+    }
+
+    @Test
+    fun testGetIconSizeStepDown_whenTaskbarIsPersistent() {
+        doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize44dp))
+            .isEqualTo(TaskbarIconSpecs.defaultPersistentIconSize)
+    }
+
+    @Test
+    fun testGetIconSizeStepDown_whenTaskbarIsTransientAndIconSizeAreInBound() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize52dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize48dp)
+    }
+
+    @Test
+    fun testGetIconSizeStepDown_whenTaskbarIsTransientAndIconSizeAreOutOfBound() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize44dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize44dp)
+    }
+
+    @Test
+    fun testGetIconSizeStepUp_whenTaskbarIsPersistent() {
+        doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize40dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize40dp)
+    }
+
+    @Test
+    fun testGetIconSizeStepUp_whenTaskbarIsTransientAndIconSizeAreInBound() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize44dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize48dp)
+    }
+
+    @Test
+    fun testGetIconSizeStepUp_whenTaskbarIsTransientAndIconSizeAreOutOfBound() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize52dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize52dp)
+    }
+}
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index ba50721..90e784d 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Legstukke gedeaktiveer in Veiligmodus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Kortpad is nie beskikbaar nie"</string>
     <string name="home_screen" msgid="5629429142036709174">"Tuis"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Verdeelde skerm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Programinligting vir %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruikinstellings vir %1$s"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 10d68aa..1289b8c 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ምግብሮች በደህንነቱ የተጠበቀ ሁኔታ ተሰናክለዋል"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"አቋራጭ አይገኝም"</string>
     <string name="home_screen" msgid="5629429142036709174">"መነሻ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"የተከፈለ ማያ ገፅ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"የመተግበሪያ መረጃ ለ%1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"የ%1$s የአጠቃቀም ቅንብሮች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 6ba2ce6..37d6fba 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات غير مفعّلة في الوضع الآمن"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
     <string name="home_screen" msgid="5629429142036709174">"الشاشة الرئيسية"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"تقسيم الشاشة"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏معلومات تطبيق %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏إعدادات استخدام \"%1$s\""</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index ce86039..e983ce8 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ৱিজেটবোৰক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"শ্বৰ্টকাট নাই"</string>
     <string name="home_screen" msgid="5629429142036709174">"গৃহ স্ক্ৰীন"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"বিভাজিত স্ক্ৰীন"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sৰ বাবে এপৰ তথ্য"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sৰ বাবে ব্যৱহাৰৰ ছেটিং"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 9ad053a..f390c7c 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidcetlər Güvənli rejimdə deaktiv edilib"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Qısayol əlçatan deyil"</string>
     <string name="home_screen" msgid="5629429142036709174">"Əsas səhifə"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekran bölünməsi"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilə bağlı tətbiq məlumatı"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s üzrə istifadə ayarları"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 408a425..fc71eeb 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u Bezbednom režimu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
     <string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podeljeni ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji za: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Podešavanja potrošnje za %1$s"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index c7ed9d9..4589125 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Віджэты адключаны ў Бяспечным рэжыме"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недаступны"</string>
     <string name="home_screen" msgid="5629429142036709174">"Галоўны экран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Падзелены экран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Інфармацыя пра праграму для: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s: налады выкарыстання"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index ce62054..643c24e 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Приспособленията са деактивирани в безопасния режим"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Няма достъп до прекия път"</string>
     <string name="home_screen" msgid="5629429142036709174">"Начален екран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделен екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Информация за приложението за %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки за използването на %1$s"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index a9f4585..7e10a0e 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"সুরক্ষিত মোডে উইজেট নিষ্ক্রিয় থাকে"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"শর্টকাটগুলি অনুপলব্ধ"</string>
     <string name="home_screen" msgid="5629429142036709174">"হোম"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"স্প্লিট স্ক্রিন"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-এর জন্য অ্যাপ সম্পর্কিত তথ্য"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-এর জন্য ব্যবহারের সেটিংস"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 325d65d..10aa86a 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u sigurnom načinu rada."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
     <string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke korištenja za: %1$s"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 50ded7b..3d2ff4a 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"En Mode segur, els widgets estan desactivats."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"La drecera no està disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Inici"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informació de l\'aplicació %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuració d\'ús de %1$s"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index c570ef1..14a3583 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
     <string name="home_screen" msgid="5629429142036709174">"Domů"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdělit obrazovku"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavení využití pro aplikaci %1$s"</string>
@@ -192,6 +194,6 @@
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Soukromé, uzamčeno."</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zamknout"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Převádění soukromého prostoru"</string>
-    <string name="ps_add_button_label" msgid="8127988716897128773">"Instalovat"</string>
+    <string name="ps_add_button_label" msgid="8127988716897128773">"Nainstalovat"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalovat aplikace do soukromého prostoru"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index e6f741f..f18e1e8 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets er deaktiveret i Beskyttet tilstand"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Genvejen er ikke tilgængelig"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startskærm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Opdel skærm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinfo for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Indstillinger for brug af %1$s"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 7f89246..9a4e5e2 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets im abgesicherten Modus deaktiviert"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Verknüpfung nicht verfügbar"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startbildschirm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Splitscreen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App-Info für %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Nutzungseinstellungen für %1$s"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 8ace970..840ff5b 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Τα γραφικά στοιχεία απενεργοποιήθηκαν στην ασφαλή λειτουργία"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Η συντόμευση δεν είναι διαθέσιμη"</string>
     <string name="home_screen" msgid="5629429142036709174">"Αρχική οθόνη"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Διαχωρισμός οθόνης"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Πληροφορίες εφαρμογής για %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Ρυθμίσεις χρήσης για %1$s"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 3fe69cd..4deacb6 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index dee4a30..e9f951b 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 3fe69cd..4deacb6 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 3fe69cd..4deacb6 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 69added..fb08c7e 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎Widgets disabled in Safe mode‎‏‎‎‏‎"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎Shortcut isn\'t available‎‏‎‎‏‎"</string>
     <string name="home_screen" msgid="5629429142036709174">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎Home‎‏‎‎‏‎"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎Split screen‎‏‎‎‏‎"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎App info for %1$s‎‏‎‎‏‎"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎Usage settings for %1$s‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 75b6979..e734744 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"El acceso directo no está disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Pantalla principal"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la app de %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración del uso de %1$s"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 91e05a7..3e0e516 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo Seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Ajustes de uso para %1$s"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 4ad9c9e..bab7da3 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Turvarežiimis on vidinad keelatud"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Otsetee pole saadaval"</string>
     <string name="home_screen" msgid="5629429142036709174">"Avakuva"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jagatud ekraanikuva"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Rakenduse teave: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Kasutuse seaded: %1$s"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 8e68ebd..b8017d6 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgetak desgaitu egin dira modu seguruan"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Lasterbideak ez daude erabilgarri"</string>
     <string name="home_screen" msgid="5629429142036709174">"Orri nagusia"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantaila zatitzea"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s aplikazioari buruzko informazioa"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s aplikazioaren erabilera-ezarpenak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index a191340..b39945b 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ابزارک‌ها در حالت ایمن غیرفعال هستند"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"میان‌بر دردسترس نیست"</string>
     <string name="home_screen" msgid="5629429142036709174">"صفحه اصلی"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"صفحهٔ دونیمه"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏اطلاعات برنامه %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏تنظیمات مصرف برای %1$s"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 00880b0..181c89e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgetit poistettu käytöstä vikasietotilassa"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pikakuvake ei ole käytettävissä."</string>
     <string name="home_screen" msgid="5629429142036709174">"Etusivu"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jaettu näyttö"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Sovellustiedot: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Käyttöasetus tälle: %1$s"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index daa4b5f..f3b08cb 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran divisé"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Renseignements sur l\'appli pour %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 580d02f..4a7c7fb 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Les widgets sont désactivés en mode sécurisé."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Raccourci non disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran partagé"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Infos sur l\'appli pour %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index fc667bc..0430a9a 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Os widgets están desactivados no modo seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atallo non está dispoñible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información da aplicación para %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración de uso para %1$s"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index ce411e6..3c248db 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"સુરક્ષિત મોડમાં વિજેટ્સ અક્ષમ કર્યા"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"શૉર્ટકટ ઉપલબ્ધ નથી"</string>
     <string name="home_screen" msgid="5629429142036709174">"હોમ સ્ક્રીન"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"સ્ક્રીનને વિભાજિત કરો"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s માટે ઍપ માહિતી"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sના વપરાશ સંબંધિત સેટિંગ"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index f1117ba..50d4762 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोड में अक्षम हैं"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नहीं है"</string>
     <string name="home_screen" msgid="5629429142036709174">"होम स्क्रीन"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s के लिए ऐप्लिकेशन की जानकारी"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s के लिए खर्च की सेटिंग"</string>
@@ -88,7 +90,7 @@
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ऐप्लिकेशन अनइंस्टॉल करें"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"इंस्‍टॉल करें"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"ऐप्लिकेशन का सुझाव न दें"</string>
-    <string name="pin_prediction" msgid="4196423321649756498">"सुझाए गए ऐप्लिकेशन को पिन करें"</string>
+    <string name="pin_prediction" msgid="4196423321649756498">"सुझाए गए ऐप पिन करें"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"शॉर्टकट इंस्‍टॉल करें"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"ऐप को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"होम स्क्रीन की सेटिंग और शॉर्टकट पढ़ने की अनुमति"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 9d60495..7a1fd94 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgeti su onemogućeni u Sigurnom načinu rada"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečac nije dostupan"</string>
     <string name="home_screen" msgid="5629429142036709174">"Početni zaslon"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni zaslon"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke upotrebe za %1$s"</string>
@@ -192,6 +194,6 @@
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privatno, zaključano."</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zaključavanje"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Prelazak na privatni prostor"</string>
-    <string name="ps_add_button_label" msgid="8127988716897128773">"Instaliraj"</string>
+    <string name="ps_add_button_label" msgid="8127988716897128773">"Instalirajte"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instaliranje aplikacija u privatni prostor"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index bb39c33..3b556da 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"A modulok ki vannak kapcsolva Csökkentett módban"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"A gyorsparancs nem áll rendelkezésre"</string>
     <string name="home_screen" msgid="5629429142036709174">"Kezdőképernyő"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Osztott képernyő"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Alkalmazásinformáció a következőhöz: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"A(z) %1$s használati beállításai"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 63b935d..491ba05 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Վիջեթներն անջատված են անվտանգ ռեժիմում"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Դյուրանցումն անհասանելի է"</string>
     <string name="home_screen" msgid="5629429142036709174">"Հիմնական էկրան"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Տրոհել էկրանը"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Տեղեկություններ %1$s հավելվածի մասին"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Օգտագործման կարգավորումներ (%1$s)"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index ca30b42..1335385 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget dinonaktifkan dalam mode Aman"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
     <string name="home_screen" msgid="5629429142036709174">"Layar utama"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Layar terpisah"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Info aplikasi untuk %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Setelan penggunaan untuk %1$s"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index aadb75e..4865264 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Græjur eru óvirkar í öruggri stillingu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Flýtileið er ekki tiltæk"</string>
     <string name="home_screen" msgid="5629429142036709174">"Heim"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skipta skjá"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Upplýsingar um forrit fyrir %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Notkunarstillingar fyrir %1$s"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 36950b8..41b4b60 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget disabilitati in modalità provvisoria"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"La scorciatoia non è disponibile"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Schermo diviso"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informazioni sull\'app %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Impostazioni di utilizzo per %1$s"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index e00e851..ae71edd 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ווידג\'טים מושבתים במצב בטוח"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"קיצור הדרך אינו זמין"</string>
     <string name="home_screen" msgid="5629429142036709174">"בית"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"מסך מפוצל"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏פרטים על האפליקציה %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏הגדרות שימוש ב-%1$s"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 1f8a93f..c64d335 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"セーフモードではウィジェットは無効です"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ショートカットは使用できません"</string>
     <string name="home_screen" msgid="5629429142036709174">"ホーム"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割画面"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s のアプリ情報"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s の使用設定"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index eac12be..bcced9a 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"უსაფრთხო რეჟიმში ვიჯეტი გამორთულია"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"მალსახმობი მიუწვდომელია"</string>
     <string name="home_screen" msgid="5629429142036709174">"მთავარი გვერდი"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ეკრანის გაყოფა"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-ის აპის ინფო"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"გამოყენების პარამეტრები %1$s-ისთვის"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 383a507..62bb983 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Қауіпсіз режимде виджеттер өшіріледі"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Таңбаша қолжетімді емес"</string>
     <string name="home_screen" msgid="5629429142036709174">"Негізгі экран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлу"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s қолданбасы туралы ақпарат"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s пайдалану параметрлері"</string>
@@ -88,7 +90,7 @@
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Қолданбаны жою"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Орнату"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Қолданба ұсынбау"</string>
-    <string name="pin_prediction" msgid="4196423321649756498">"Болжанған қолданбаны бекіту"</string>
+    <string name="pin_prediction" msgid="4196423321649756498">"Болжамды бекіту"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"таңбаша орнату"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Қолданбаға пайдаланушының қатысуынсыз төте пернелерді қосу мүмкіндігін береді."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"негізгі экран параметрлері мен таңбашаларын оқу"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index e319852..b733699 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"បាន​បិទ​ធាតុ​ក្រាហ្វិក​ក្នុង​របៀប​សុវត្ថិភាព"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ផ្លូវកាត់មិនអាចប្រើបានទេ"</string>
     <string name="home_screen" msgid="5629429142036709174">"អេក្រង់ដើម"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"មុខងារ​បំបែកអេក្រង់"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ព័ត៌មានកម្មវិធី​សម្រាប់ %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"ការកំណត់ការប្រើប្រាស់សម្រាប់ %1$s"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 758d47c..16b39f8 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ಸುರಕ್ಷಿತ ಮೋಡ್‌ನಲ್ಲಿ ವಿಜೆಟ್‌ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ಶಾರ್ಟ್‌ಕಟ್ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="home_screen" msgid="5629429142036709174">"ಹೋಮ್"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ಗಾಗಿ ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ಗೆ ಸಂಬಂಧಿಸಿದ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
@@ -85,7 +87,7 @@
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ಖಾಸಗಿಯಾಗಿ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
-    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ಆ್ಯಪ್‌ ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ಆ್ಯಪ್‌ ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"ಸ್ಥಾಪಿಸಿ"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"ಆ್ಯಪ್ ಅನ್ನು ಸೂಚಿಸಬೇಡಿ"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"ಮುನ್ನೋಟ ಪಿನ್ ಮಾಡಿ"</string>
@@ -192,6 +194,6 @@
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ಖಾಸಗಿ, ಲಾಕ್ ಮಾಡಲಾಗಿದೆ."</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"ಲಾಕ್ ಮಾಡಿ"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"ಖಾಸಗಿ ಸ್ಪೇಸ್ ಪರಿವರ್ತನೆಯಾಗುತ್ತಿದೆ"</string>
-    <string name="ps_add_button_label" msgid="8127988716897128773">"ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+    <string name="ps_add_button_label" msgid="8127988716897128773">"ಇನ್‌ಸ್ಟಾಲ್"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"ಆ್ಯಪ್‌ಗಳನ್ನು ಪ್ರೈವೇಟ್ ಸ್ಪೇಸ್‌ನಲ್ಲಿ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 4d623fa..1d5c58f 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"안전 모드에서 위젯 사용 중지됨"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"바로가기를 사용할 수 없음"</string>
     <string name="home_screen" msgid="5629429142036709174">"홈"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"화면 분할"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 앱 정보"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s의 사용량 설정"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 98d1e73..6f01db3 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виджеттер Коопсуз режимде өчүрүлгөн"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Кыска жол жок"</string>
     <string name="home_screen" msgid="5629429142036709174">"Башкы экран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлүү"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s колдонмосу жөнүндө маалымат"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s колдонмосун пайдалануу параметрлери"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index e0bcfe0..7ec103b 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"​ວິດ​ເຈັດ​ຖືກ​ປິດ​ໃນ Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ບໍ່ສາມາດໃຊ້ທາງລັດໄດ້"</string>
     <string name="home_screen" msgid="5629429142036709174">"ໂຮມສະກຣີນ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ແບ່ງໜ້າຈໍ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ຂໍ້ມູນແອັບສຳລັບ %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"ການຕັ້ງຄ່າການນຳໃຊ້ສຳລັບ %1$s"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 6a2b8d7..1112474 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Valdikliai išjungti Saugiame režime"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
     <string name="home_screen" msgid="5629429142036709174">"Pagrindinis"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Programos „%1$s“ informacija"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"„%1$s“ naudojimo nustatymai"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index cae6b6e..4f1a92c 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Logrīki atspējoti drošajā režīmā"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Saīsne nav pieejama."</string>
     <string name="home_screen" msgid="5629429142036709174">"Sākums"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Sadalīt ekrānu"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s: informācija par lietotni"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Lietojuma iestatījumi: %1$s"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 46d8600..d984ca0 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Додатоците се оневозможени во безбеден режим"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Кратенката не е достапна"</string>
     <string name="home_screen" msgid="5629429142036709174">"Почетен екран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Поделен екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Податоци за апликација за %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Поставки за користење за %1$s"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index eec5f74..ea8849e 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"സുരക്ഷിത മോഡിൽ വിജറ്റുകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"കുറുക്കുവഴി ലഭ്യമല്ല"</string>
     <string name="home_screen" msgid="5629429142036709174">"ഹോം"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"സ്‌ക്രീൻ വിഭജന മോഡ്"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s എന്നതിന്റെ ആപ്പ് വിവരങ്ങൾ"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s എന്നതിനുള്ള ഉപയോഗ ക്രമീകരണം"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 396589c..8554717 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Safe горимд виджетүүдийг идэвхгүйжүүлсэн"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Товчлол алга"</string>
     <string name="home_screen" msgid="5629429142036709174">"Нүүр"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Дэлгэцийг хуваах"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-н аппын мэдээлэл"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-н ашиглалтын тохиргоо"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 972c9f2..dcdf25c 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोडमध्ये अक्षम झाले"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नाही"</string>
     <string name="home_screen" msgid="5629429142036709174">"होम"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s साठी ॲपशी संबंधित माहिती"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s साठी वापरासंबंधित सेटिंग्ज"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index c25e3df..22ee2cb 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget dilumpuhkan dalam mod Selamat"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
     <string name="home_screen" msgid="5629429142036709174">"Rumah"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skrin pisah"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Maklumat apl untuk %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Tetapan penggunaan sebanyak %1$s"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 700278a..bca1c6e 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"လုံခြုံရေး မုဒ်ထဲမှာ ဝီဂျက်များကို ပိတ်ထား"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ဖြတ်လမ်း မရနိုင်ပါ"</string>
     <string name="home_screen" msgid="5629429142036709174">"ပင်မစာမျက်နှာ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s အတွက် အက်ပ်အချက်အလက်"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s အတွက် အသုံးပြုမှုဆက်တင်များ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index c2f99aa..1440443 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Moduler er deaktivert i sikker modus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Snarveien er ikke tilgjengelig"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startskjerm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delt skjerm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformasjon for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Bruksinnstillinger for %1$s"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 5e93f89..52613cb 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"सुरक्षित मोडमा विगेटहरू अक्षम गरियो"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"सर्टकट उपलब्ध छैन"</string>
     <string name="home_screen" msgid="5629429142036709174">"होम"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रिन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s का हकमा एपसम्बन्धी जानकारी"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s को प्रयोगसम्बन्धी सेटिङ"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index c2e5ebd..90dcd46 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets uitgezet in veilige modus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Snelkoppeling is niet beschikbaar"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startscherm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gesplitst scherm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App-info voor %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruiksinstellingen voor %1$s"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 3c240ae..6d2f87e 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ନିରାପଦ ମୋଡରେ ୱିଜେଟ୍‌ ଅକ୍ଷମ କରାଗଲା"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ଶର୍ଟକଟ୍‌ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="home_screen" msgid="5629429142036709174">"ହୋମ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ସ୍କ୍ରିନ‌କୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ପାଇଁ ଆପ ସୂଚନା"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ପାଇଁ ବ୍ୟବହାର ସେଟିଂସ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 0df0c3f..c235c3c 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ਵਿਜੇਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="home_screen" msgid="5629429142036709174">"ਮੁੱਖ ਪੰਨਾ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ਲਈ ਐਪ ਜਾਣਕਾਰੀ"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ਲਈ ਵਰਤੋਂ ਸੈਟਿੰਗਾਂ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index a8e79e3..685b7d9 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widżety są wyłączone w trybie bezpiecznym"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Skrót nie jest dostępny"</string>
     <string name="home_screen" msgid="5629429142036709174">"Ekran główny"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podziel ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacje o aplikacji: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s – ustawienia użycia"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 6e4a8e8..14a3cf5 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no Modo de segurança"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
     <string name="home_screen" msgid="5629429142036709174">"Página inicial"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecrã dividido"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações da app para %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Definições de utilização para %1$s"</string>
@@ -190,7 +192,7 @@
     <string name="ps_container_settings" msgid="6059734123353320479">"Definições do espaço privado"</string>
     <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privado, desbloqueado."</string>
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privado, bloqueado."</string>
-    <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
+    <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloqueio"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Transição do espaço privado"</string>
     <string name="ps_add_button_label" msgid="8127988716897128773">"Instalar"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instale apps no espaço privado"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index a7eb0a7..b2ec45d 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no modo de segurança"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
     <string name="home_screen" msgid="5629429142036709174">"Início"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Tela dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações do app %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Configurações de uso de %1$s"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index bbade3c..256fa60 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgeturile sunt dezactivate în modul de siguranță"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
     <string name="home_screen" msgid="5629429142036709174">"Pagina de pornire"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecran împărțit"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informații despre aplicație pentru %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Setări de utilizare pentru %1$s"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 995052e..402143d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виджеты отключены в безопасном режиме"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недоступен"</string>
     <string name="home_screen" msgid="5629429142036709174">"Главный экран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделить экран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Сведения о приложении \"%1$s\""</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки использования приложения \"%1$s\""</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 9140854..455f18b 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"සුරක්ෂිත ආකාරය තුළ විජටය අබල කරන ලදි"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"කෙටි මග ලබා ගත නොහැකිය"</string>
     <string name="home_screen" msgid="5629429142036709174">"මුල් පිටුව"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"බෙදුම් තිරය"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s සඳහා යෙදුම් තතු"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s සඳහා භාවිත සැකසීම්"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 1ba5771..7771cf7 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikácie sú v núdzovom režime zakázané"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Skratky nie sú k dispozícii"</string>
     <string name="home_screen" msgid="5629429142036709174">"Domov"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdeliť obrazovku"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informácie o aplikácii pre %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavenia používania pre %1$s"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index cea6e18..2e75b17 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Pripomočki so onemogočeni v varnem načinu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Bližnjica ni na voljo"</string>
     <string name="home_screen" msgid="5629429142036709174">"Začetni zaslon"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Razdeljen zaslon"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Podatki o aplikaciji za: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavitve uporabe za »%1$s«"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index f328557..a00f11a 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikacionet janë të çaktivizuara në modalitetin e sigurt"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shkurtorja nuk është e disponueshme"</string>
     <string name="home_screen" msgid="5629429142036709174">"Ekrani bazë"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekrani i ndarë"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacioni i aplikacionit për %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Cilësimet e përdorimit për \"%1$s\""</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 6fdeb41..97d3ff4 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виџети су онемогућени у Безбедном режиму"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Пречица није доступна"</string>
     <string name="home_screen" msgid="5629429142036709174">"Почетни екран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Подељени екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Информације о апликацији за: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Подешавања потрошње за %1$s"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 1866f95..8a8524e 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets är inaktiverade i felsäkert läge"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Genvägen är inte tillgänglig"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startskärm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delad skärm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformation för %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Användningsinställningar för %1$s"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 5c02beb..64f6296 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Wijeti zimezimwa katika hali ya Usalama"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Hakuna njia ya mkato"</string>
     <string name="home_screen" msgid="5629429142036709174">"Skrini ya kwanza"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gawa skrini"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Maelezo ya programu ya %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Mipangilio ya matumizi ya %1$s"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index c45b3dd..189f4ce 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"பாதுகாப்புப் பயன்முறையில் விட்ஜெட்கள் முடக்கப்பட்டுள்ளன"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ஷார்ட்கட் இல்லை"</string>
     <string name="home_screen" msgid="5629429142036709174">"முகப்பு"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"திரைப் பிரிப்பு"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sக்கான ஆப்ஸ் தகவல்கள்"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sக்கான உபயோக அமைப்புகள்"</string>
@@ -88,7 +90,7 @@
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ஆப்ஸை நிறுவல் நீக்கு"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"நிறுவு"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"பரிந்துரைக்காதே"</string>
-    <string name="pin_prediction" msgid="4196423321649756498">"கணிக்கப்பட்ட ஆப்ஸைப் பின் செய்தல்"</string>
+    <string name="pin_prediction" msgid="4196423321649756498">"கணிக்கப்பட்டதைப் பின் செய்"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"குறுக்குவழிகளை நிறுவுதல்"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"பயனரின் அனுமதி இல்லாமல் குறுக்குவழிகளைச் சேர்க்கப் ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"முகப்புத் திரையின் அமைப்புகளையும் ஷார்ட்கட்களையும் படித்தல்"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index e1c4668..d789baa 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"సురక్షిత మోడ్‌లో విడ్జెట్‌లు నిలిపివేయబడ్డాయి"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"షార్ట్‌కట్ అందుబాటులో లేదు"</string>
     <string name="home_screen" msgid="5629429142036709174">"మొదటి ట్యాబ్"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"స్ప్లిట్ స్క్రీన్"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s కోసం యాప్ సమాచారం"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sకు సంబంధించిన వినియోగ సెట్టింగ్‌లు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 4288c24..f89638d 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"มีการปิดใช้งานวิดเจ็ตในเซฟโหมด"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ทางลัดไม่พร้อมใช้งาน"</string>
     <string name="home_screen" msgid="5629429142036709174">"หน้าแรก"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"แยกหน้าจอ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ข้อมูลแอปสำหรับ %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"การตั้งค่าการใช้งานสำหรับ %1$s"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 305f532..8754a12 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Naka-disable ang mga widget sa Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Hindi available ang shortcut"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Impormasyon ng app para sa %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Mga setting ng paggamit para sa %1$s"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index e8c7593..761ce56 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Güvenli modda widget\'lar devre dışı"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Kısayol kullanılamıyor"</string>
     <string name="home_screen" msgid="5629429142036709174">"Ana ekran"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Bölünmüş ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s uygulama bilgileri"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ile ilgili kullanım ayarları"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index c1d6b2f..2c85d5a 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"У безпечному режимі віджети вимкнено"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлик недоступний"</string>
     <string name="home_screen" msgid="5629429142036709174">"Головний екран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Розділити екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Інформація про додаток для %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Параметри використання (%1$s)"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 6c1a88e..09f4304 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ویجیٹس کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"شارٹ کٹ دستیاب نہیں ہے"</string>
     <string name="home_screen" msgid="5629429142036709174">"ہوم"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"اسپلٹ اسکرین"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏%1$s کے لیے ایپ کی معلومات"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏%1$s کیلئے استعمال کی ترتیبات"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 18431cc..d8dea69 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Xavfsiz rejimda vidjetlar o‘chirib qo‘yilgan"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Tezkor tugmadan foydalanib bo‘lmaydi"</string>
     <string name="home_screen" msgid="5629429142036709174">"Bosh ekran"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekranni ikkiga ajratish"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilovasi axboroti"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s uchun sarf sozlamalari"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b5fb1d7..172c995 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích bị vô hiệu hóa ở chế độ an toàn"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
     <string name="home_screen" msgid="5629429142036709174">"Màn hình chính"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Chia đôi màn hình"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Thông tin ứng dụng cho %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Chế độ cài đặt mức sử dụng %1$s"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index d51f8fd..9c658dc 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用微件"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"无法使用快捷方式"</string>
     <string name="home_screen" msgid="5629429142036709174">"主屏幕"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分屏"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的应用信息"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s的使用设置"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 90ff515..af32638 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式中無法使用小工具"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"沒有可用的捷徑"</string>
     <string name="home_screen" msgid="5629429142036709174">"主畫面"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割螢幕"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的應用程式資料"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 023a526..54a1c5d 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式下無法使用小工具"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"目前無法使用捷徑"</string>
     <string name="home_screen" msgid="5629429142036709174">"主畫面"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割畫面"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"「%1$s」的應用程式資訊"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
@@ -87,7 +89,7 @@
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"安裝在私人空間中"</string>
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"解除安裝應用程式"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"安裝"</string>
-    <string name="dismiss_prediction_label" msgid="3357562989568808658">"不要提供應用程式建議"</string>
+    <string name="dismiss_prediction_label" msgid="3357562989568808658">"不要建議此應用程式"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"固定預測的應用程式"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式自動新增捷徑。"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index a936725..9752e18 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Amawijethi akhutshaziwe kwimodi yokuphepha"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Isinqamuleli asitholakali"</string>
     <string name="home_screen" msgid="5629429142036709174">"Ikhaya"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Hlukanisa isikrini"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Ulwazi lwe-App ye-%1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Amasethingi okusetshenziswa ka-%1$s"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index aa83c01..05724e2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -448,6 +448,9 @@
     <!-- Size of the maximum radius for the enforced rounded rectangles. -->
     <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
 
+    <!-- Size of the radius for the rounded corners of Persistent Taskbar. -->
+    <dimen name="persistent_taskbar_corner_radius">16dp</dimen>
+
     <!-- Base Swipe Detector, speed in dp/s -->
     <dimen name="base_swift_detector_fling_release_velocity">1dp</dimen>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a9cca6d..d33adc4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,6 +36,8 @@
     <string name="shortcut_not_available">Shortcut isn\'t available</string>
     <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
     <string name="home_screen">Home</string>
+    <!-- Description for setting the current launcher as the default home app. [CHAR_LIMIT=none]-->
+    <string name="set_default_home_app">Set <xliff:g id="launcher_name" example="Launcher3">%1$s</xliff:g> as default home app in Settings</string>
 
     <!-- Options for recent tasks -->
     <!-- Title for an option to enter split screen mode for a given app -->
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0daabb1..00db3a3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -53,7 +53,6 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
@@ -714,7 +713,7 @@
         overviewTaskThumbnailTopMarginPx =
                 enableOverviewIconMenu() ? 0 : overviewTaskIconSizePx + overviewTaskMarginPx;
         // Don't add margin with floating search bar to minimize risk of overlapping.
-        overviewActionsTopMarginPx = FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() ? 0
+        overviewActionsTopMarginPx = Flags.floatingSearchBar() ? 0
                 : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
         overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
         overviewActionsButtonSpacing = res.getDimensionPixelSize(
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 088983e..4e566ab 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -38,6 +38,7 @@
 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_HOME_ROLE;
 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;
@@ -151,6 +152,7 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
+import android.widget.Toast;
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
 
@@ -310,7 +312,7 @@
 
     private static boolean sIsNewProcess = true;
 
-    private StateManager<LauncherState> mStateManager;
+    private StateManager<LauncherState, Launcher> mStateManager;
 
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
 
@@ -892,6 +894,17 @@
         }
         mPendingActivityResult = null;
 
+        if (requestCode == REQUEST_HOME_ROLE) {
+            if (resultCode != RESULT_OK) {
+                Toast.makeText(
+                        this,
+                        this.getString(R.string.set_default_home_app,
+                                this.getString(R.string.derived_app_name)),
+                        Toast.LENGTH_LONG).show();
+            }
+            return;
+        }
+
         // Reset the startActivity waiting flag
         final PendingRequestArgs requestArgs = mPendingRequestArgs;
         setWaitingForResult(null);
@@ -2762,7 +2775,7 @@
     }
 
     @Override
-    protected void collectStateHandlers(List<StateHandler> out) {
+     public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
         out.add(getAllAppsController());
         out.add(getWorkspace());
     }
@@ -2983,7 +2996,7 @@
     }
 
     @Override
-    public StateManager<LauncherState> getStateManager() {
+    public StateManager<LauncherState, Launcher> getStateManager() {
         return mStateManager;
     }
 
diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java
index 1abfeb9..445fb41 100644
--- a/src/com/android/launcher3/LauncherConstants.java
+++ b/src/com/android/launcher3/LauncherConstants.java
@@ -41,6 +41,7 @@
 
         public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
         public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
+        public static final int REQUEST_HOME_ROLE = 14;
         static final int REQUEST_CREATE_SHORTCUT = 1;
         static final int REQUEST_CREATE_APPWIDGET = 5;
         static final int REQUEST_PICK_APPWIDGET = 9;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 47a7115..ca1b2a9 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -480,7 +480,7 @@
                 }
             }
 
-            if (!removedIds.isEmpty()) {
+            if (!removedIds.isEmpty() && !isAppArchived) {
                 taskController.deleteAndBindComponentsRemoved(
                         ItemInfoMatcher.ofItemIds(removedIds),
                         "removed because install session failed");
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index d2d56f2..102189b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -440,7 +440,7 @@
      */
     public void onBackInvoked(Launcher launcher) {
         if (this != NORMAL) {
-            StateManager<LauncherState> lsm = launcher.getStateManager();
+            StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
             LauncherState lastState = lsm.getLastState();
             lsm.goToState(lastState, forEndCallback(this::onBackAnimationCompleted));
         }
@@ -461,7 +461,7 @@
      */
     public void onBackProgressed(
             Launcher launcher, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
-        StateManager<LauncherState> lsm = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
         LauncherState toState = lsm.getLastState();
         lsm.onBackProgressed(toState, backProgress);
     }
@@ -471,7 +471,7 @@
      * predictive back gesture.
      */
     public void onBackCancelled(Launcher launcher) {
-        StateManager<LauncherState> lsm = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
         LauncherState toState = lsm.getLastState();
         lsm.onBackCancelled(toState);
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 584d089..e601a3e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -526,7 +526,7 @@
         }
 
         updateChildrenLayersEnabled();
-        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = mLauncher.getStateManager();
         stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
             @Override
             public void onStateTransitionComplete(LauncherState finalState) {
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 63a168e..1e0f289 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -56,7 +56,7 @@
             return false;
         }
         Launcher launcher = mActivityContext;
-        StateManager<LauncherState> manager = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> manager = launcher.getStateManager();
         if (manager.isInTransition() && manager.getTargetState() != null) {
             return manager.getTargetState().shouldFloatingSearchBarUsePillWhenUnfocused(launcher);
         }
@@ -70,7 +70,7 @@
             return super.getFloatingSearchBarRestingMarginBottom();
         }
         Launcher launcher = mActivityContext;
-        StateManager<LauncherState> stateManager = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = launcher.getStateManager();
 
         // We want to rest at the current state's resting position, unless we are in transition and
         // the target state's resting position is higher (that way if we are closing the keyboard,
@@ -95,7 +95,7 @@
             return super.getFloatingSearchBarRestingMarginStart();
         }
 
-        StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = mActivityContext.getStateManager();
 
         // Special case to not expand the search bar when exiting All Apps on phones.
         if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
@@ -117,7 +117,7 @@
             return super.getFloatingSearchBarRestingMarginEnd();
         }
 
-        StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = mActivityContext.getStateManager();
 
         // Special case to not expand the search bar when exiting All Apps on phones.
         if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 5cacf60..6f021ea 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -281,11 +281,29 @@
 
     @Override
     public void setQuietMode(boolean enable) {
-        super.setQuietMode(enable);
+        UI_HELPER_EXECUTOR.post(() ->
+                mUserCache.getUserProfiles()
+                        .stream()
+                        .filter(getUserMatcher())
+                        .findFirst()
+                        .ifPresent(userHandle -> setQuietModeSafely(enable, userHandle)));
         mReadyToAnimate = true;
     }
 
     /**
+     * Sets Quiet Mode for Private Profile.
+     * If {@link SecurityException} is thrown, prompts the user to set this launcher as HOME app.
+     */
+    private void setQuietModeSafely(boolean enable, UserHandle userHandle) {
+        try {
+            mUserManager.requestQuietModeEnabled(enable, userHandle);
+        } catch (SecurityException ex) {
+            ApiWrapper.INSTANCE.get(mAllApps.mActivityContext)
+                    .assignDefaultHomeRole(mAllApps.mActivityContext);
+        }
+    }
+
+    /**
      * Expand the private space after the app list has been added and updated from
      * {@link AlphabeticalAppsList#onAppsUpdated()}
      */
diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java
index eb74d20..93b6b29 100644
--- a/src/com/android/launcher3/allapps/UserProfileManager.java
+++ b/src/com/android/launcher3/allapps/UserProfileManager.java
@@ -55,12 +55,11 @@
     public @interface UserProfileState { }
 
     protected final StatsLogManager mStatsLogManager;
+    protected final UserManager mUserManager;
+    protected final UserCache mUserCache;
+
     @UserProfileState
     private int mCurrentState;
-
-    private final UserManager mUserManager;
-    private final UserCache mUserCache;
-
     protected UserProfileManager(UserManager userManager,
             StatsLogManager statsLogManager,
             UserCache userCache) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4b908bf..33e6f91 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -84,11 +84,6 @@
                     + "data preparation for loading the home screen");
 
     // TODO(Block 4): Cleanup flags
-    public static final BooleanFlag ENABLE_FLOATING_SEARCH_BAR =
-            getReleaseFlag(268388460, "ENABLE_FLOATING_SEARCH_BAR", DISABLED,
-                    "Allow search bar to persist and animate across states, and attach to"
-                            + " the keyboard from the bottom of the screen");
-
     public static final BooleanFlag ENABLE_ALL_APPS_FROM_OVERVIEW =
             getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
                     "Allow entering All Apps from Overview (e.g. long swipe up from app)");
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index cc20cd1..f443f81 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -96,7 +96,7 @@
                 .collect(groupingBy(SessionInfo::getInstallerPackageName,
                         mapping(SessionInfo::getAppPackageName, Collectors.toSet())))
                 .forEach((installer, packages) ->
-                    sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
+                        sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
     }
 
     /**
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
new file mode 100644
index 0000000..aa62c32
--- /dev/null
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2024 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.model
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageInstaller.SessionInfo
+import android.os.Process
+import android.util.Log
+import androidx.annotation.AnyThread
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.model.data.CollectionInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.InstallSessionHelper
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+
+/**
+ * Helper class to send broadcasts to package installers that have:
+ * - Pending Items on first screen
+ * - Installed/Archived Items on first screen
+ * - Installed/Archived Widgets on every screen
+ *
+ * The packages are broken down by: folder items, workspace items, hotseat items, and widgets.
+ * Package installers only receive data for items that they are installing or have installed.
+ */
+object FirstScreenBroadcastHelper {
+    @VisibleForTesting const val MAX_BROADCAST_SIZE = 70
+
+    private const val TAG = "FirstScreenBroadcastHelper"
+    private const val DEBUG = true
+    private const val ACTION_FIRST_SCREEN_ACTIVE_INSTALLS =
+        "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS"
+    // String retained as "folderItem" for back-compatibility reasons.
+    private const val PENDING_COLLECTION_ITEM_EXTRA = "folderItem"
+    private const val PENDING_WORKSPACE_ITEM_EXTRA = "workspaceItem"
+    private const val PENDING_HOTSEAT_ITEM_EXTRA = "hotseatItem"
+    private const val PENDING_WIDGET_ITEM_EXTRA = "widgetItem"
+    // Extras containing all installed items, including Archived Apps.
+    private const val INSTALLED_WORKSPACE_ITEMS_EXTRA = "workspaceInstalledItems"
+    private const val INSTALLED_HOTSEAT_ITEMS_EXTRA = "hotseatInstalledItems"
+    // This includes installed widgets on all screens, not just first.
+    private const val ALL_INSTALLED_WIDGETS_ITEM_EXTRA = "widgetInstalledItems"
+    private const val VERIFICATION_TOKEN_EXTRA = "verificationToken"
+
+    /**
+     * Return list of [FirstScreenBroadcastModel] for each installer and their
+     * installing/installed/archived items. If the FirstScreenBroadcastModel data is greater in size
+     * than [MAX_BROADCAST_SIZE], then we will truncate the data until it meets the size limit to
+     * avoid overloading the broadcast.
+     *
+     * @param packageManagerHelper helper for querying PackageManager
+     * @param firstScreenItems every ItemInfo on first screen
+     * @param userKeyToSessionMap map of pending SessionInfo's for installing items
+     * @param allWidgets list of all Widgets added to every screen
+     */
+    @WorkerThread
+    @JvmStatic
+    fun createModelsForFirstScreenBroadcast(
+        packageManagerHelper: PackageManagerHelper,
+        firstScreenItems: List<ItemInfo>,
+        userKeyToSessionMap: Map<PackageUserKey, SessionInfo>,
+        allWidgets: List<LauncherAppWidgetInfo>
+    ): List<FirstScreenBroadcastModel> {
+
+        // installers for installing items
+        val pendingItemInstallerMap: Map<String, MutableSet<String>> =
+            createPendingItemsMap(userKeyToSessionMap)
+        val installingPackages = pendingItemInstallerMap.values.flatten().toSet()
+
+        // installers for installed items on first screen
+        val installedItemInstallerMap: Map<String, MutableSet<ItemInfo>> =
+            createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper)
+
+        // installers for widgets on all screens
+        val allInstalledWidgetsMap: Map<String, MutableSet<LauncherAppWidgetInfo>> =
+            createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper)
+
+        val allInstallers: Set<String> =
+            pendingItemInstallerMap.keys +
+                installedItemInstallerMap.keys +
+                allInstalledWidgetsMap.keys
+        val models = mutableListOf<FirstScreenBroadcastModel>()
+        // create broadcast for each installer, with extras for each item category
+        allInstallers.forEach { installer ->
+            val installingItems = pendingItemInstallerMap[installer]
+            val broadcastModel =
+                FirstScreenBroadcastModel(installerPackage = installer).apply {
+                    addPendingItems(installingItems, firstScreenItems)
+                    addInstalledItems(installer, installedItemInstallerMap)
+                    addAllScreenWidgets(installer, allInstalledWidgetsMap)
+                }
+            broadcastModel.truncateModelForBroadcast()
+            models.add(broadcastModel)
+        }
+        return models
+    }
+
+    /** From the model data, create Intents to send broadcasts and fire them. */
+    @WorkerThread
+    @JvmStatic
+    fun sendBroadcastsForModels(context: Context, models: List<FirstScreenBroadcastModel>) {
+        for (model in models) {
+            model.printDebugInfo()
+            val intent =
+                Intent(ACTION_FIRST_SCREEN_ACTIVE_INSTALLS)
+                    .setPackage(model.installerPackage)
+                    .putExtra(
+                        VERIFICATION_TOKEN_EXTRA,
+                        PendingIntent.getActivity(
+                            context,
+                            0 /* requestCode */,
+                            Intent(),
+                            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+                        )
+                    )
+                    .putStringArrayListExtra(
+                        PENDING_COLLECTION_ITEM_EXTRA,
+                        ArrayList(model.pendingCollectionItems)
+                    )
+                    .putStringArrayListExtra(
+                        PENDING_WORKSPACE_ITEM_EXTRA,
+                        ArrayList(model.pendingWorkspaceItems)
+                    )
+                    .putStringArrayListExtra(
+                        PENDING_HOTSEAT_ITEM_EXTRA,
+                        ArrayList(model.pendingHotseatItems)
+                    )
+                    .putStringArrayListExtra(
+                        PENDING_WIDGET_ITEM_EXTRA,
+                        ArrayList(model.pendingWidgetItems)
+                    )
+                    .putStringArrayListExtra(
+                        INSTALLED_WORKSPACE_ITEMS_EXTRA,
+                        ArrayList(model.installedWorkspaceItems)
+                    )
+                    .putStringArrayListExtra(
+                        INSTALLED_HOTSEAT_ITEMS_EXTRA,
+                        ArrayList(model.installedHotseatItems)
+                    )
+                    .putStringArrayListExtra(
+                        ALL_INSTALLED_WIDGETS_ITEM_EXTRA,
+                        ArrayList(
+                            model.firstScreenInstalledWidgets +
+                                model.secondaryScreenInstalledWidgets
+                        )
+                    )
+            context.sendBroadcast(intent)
+        }
+    }
+
+    /** Maps Installer packages to Set of app packages from install sessions */
+    private fun createPendingItemsMap(
+        userKeyToSessionMap: Map<PackageUserKey, SessionInfo>
+    ): Map<String, MutableSet<String>> {
+        val myUser = Process.myUserHandle()
+        val result = mutableMapOf<String, MutableSet<String>>()
+        userKeyToSessionMap.forEach { entry ->
+            if (!myUser.equals(InstallSessionHelper.getUserHandle(entry.value))) return@forEach
+            val installer = entry.value.installerPackageName
+            val appPackage = entry.value.appPackageName
+            if (installer.isNullOrEmpty() || appPackage.isNullOrEmpty()) return@forEach
+            result.getOrPut(installer) { mutableSetOf() }.add(appPackage)
+        }
+        return result
+    }
+
+    /**
+     * Maps Installer packages to Set of ItemInfo from first screen. Filter out installing packages.
+     */
+    private fun createInstalledItemsMap(
+        firstScreenItems: List<ItemInfo>,
+        installingPackages: Set<String>,
+        packageManagerHelper: PackageManagerHelper
+    ): Map<String, MutableSet<ItemInfo>> {
+        val result = mutableMapOf<String, MutableSet<ItemInfo>>()
+        firstScreenItems.forEach { item ->
+            val appPackage = getPackageName(item) ?: return@forEach
+            if (installingPackages.contains(appPackage)) return@forEach
+            val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
+            if (installer.isNullOrEmpty()) return@forEach
+            result.getOrPut(installer) { mutableSetOf() }.add(item)
+        }
+        return result
+    }
+
+    /**
+     * Maps Installer packages to Set of AppWidget packages installed on all screens. Filter out
+     * installing packages.
+     */
+    private fun createAllInstalledWidgetsMap(
+        allWidgets: List<LauncherAppWidgetInfo>,
+        installingPackages: Set<String>,
+        packageManagerHelper: PackageManagerHelper
+    ): Map<String, MutableSet<LauncherAppWidgetInfo>> {
+        val result = mutableMapOf<String, MutableSet<LauncherAppWidgetInfo>>()
+        allWidgets
+            .sortedBy { widget -> widget.screenId }
+            .forEach { widget ->
+                val appPackage = getPackageName(widget) ?: return@forEach
+                if (installingPackages.contains(appPackage)) return@forEach
+                val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
+                if (installer.isNullOrEmpty()) return@forEach
+                result.getOrPut(installer) { mutableSetOf() }.add(widget)
+            }
+        return result
+    }
+
+    /**
+     * Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer
+     */
+    private fun FirstScreenBroadcastModel.addPendingItems(
+        installingItems: Set<String>?,
+        firstScreenItems: List<ItemInfo>
+    ) {
+        if (installingItems == null) return
+        for (info in firstScreenItems) {
+            addCollectionItems(info, installingItems)
+            val packageName = getPackageName(info) ?: continue
+            if (!installingItems.contains(packageName)) continue
+            when {
+                info is LauncherAppWidgetInfo -> pendingWidgetItems.add(packageName)
+                info.container == CONTAINER_HOTSEAT -> pendingHotseatItems.add(packageName)
+                info.container == CONTAINER_DESKTOP -> pendingWorkspaceItems.add(packageName)
+            }
+        }
+    }
+
+    /**
+     * Add first screen installed Items from Map to [FirstScreenBroadcastModel] for given installer
+     */
+    private fun FirstScreenBroadcastModel.addInstalledItems(
+        installer: String,
+        installedItemInstallerMap: Map<String, Set<ItemInfo>>,
+    ) {
+        installedItemInstallerMap[installer]?.forEach { info ->
+            val packageName: String = getPackageName(info) ?: return@forEach
+            when (info.container) {
+                CONTAINER_HOTSEAT -> installedHotseatItems.add(packageName)
+                CONTAINER_DESKTOP -> installedWorkspaceItems.add(packageName)
+            }
+        }
+    }
+
+    /** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */
+    private fun FirstScreenBroadcastModel.addAllScreenWidgets(
+        installer: String,
+        allInstalledWidgetsMap: Map<String, Set<LauncherAppWidgetInfo>>
+    ) {
+        allInstalledWidgetsMap[installer]?.forEach { widget ->
+            val packageName: String = getPackageName(widget) ?: return@forEach
+            if (widget.screenId == 0) {
+                firstScreenInstalledWidgets.add(packageName)
+            } else {
+                secondaryScreenInstalledWidgets.add(packageName)
+            }
+        }
+    }
+
+    private fun FirstScreenBroadcastModel.addCollectionItems(
+        info: ItemInfo,
+        installingPackages: Set<String>
+    ) {
+        if (info !is CollectionInfo) return
+        pendingCollectionItems.addAll(
+            cloneOnMainThread(info.getAppContents())
+                .mapNotNull { getPackageName(it) }
+                .filter { installingPackages.contains(it) }
+        )
+    }
+
+    /**
+     * Creates a copy of [FirstScreenBroadcastModel] with items truncated to meet
+     * [MAX_BROADCAST_SIZE] in a prioritized order.
+     */
+    @VisibleForTesting
+    fun FirstScreenBroadcastModel.truncateModelForBroadcast() {
+        val totalItemCount = getTotalItemCount()
+        if (totalItemCount <= MAX_BROADCAST_SIZE) return
+        var extraItemCount = totalItemCount - MAX_BROADCAST_SIZE
+
+        while (extraItemCount > 0) {
+            // In this order, remove items until we meet the max size limit.
+            when {
+                pendingCollectionItems.isNotEmpty() ->
+                    pendingCollectionItems.apply { remove(last()) }
+                pendingHotseatItems.isNotEmpty() -> pendingHotseatItems.apply { remove(last()) }
+                installedHotseatItems.isNotEmpty() -> installedHotseatItems.apply { remove(last()) }
+                secondaryScreenInstalledWidgets.isNotEmpty() ->
+                    secondaryScreenInstalledWidgets.apply { remove(last()) }
+                pendingWidgetItems.isNotEmpty() -> pendingWidgetItems.apply { remove(last()) }
+                firstScreenInstalledWidgets.isNotEmpty() ->
+                    firstScreenInstalledWidgets.apply { remove(last()) }
+                pendingWorkspaceItems.isNotEmpty() -> pendingWorkspaceItems.apply { remove(last()) }
+                installedWorkspaceItems.isNotEmpty() ->
+                    installedWorkspaceItems.apply { remove(last()) }
+            }
+            extraItemCount--
+        }
+    }
+
+    /** Returns count of all Items held by [FirstScreenBroadcastModel]. */
+    @VisibleForTesting
+    fun FirstScreenBroadcastModel.getTotalItemCount() =
+        pendingCollectionItems.size +
+            pendingWorkspaceItems.size +
+            pendingHotseatItems.size +
+            pendingWidgetItems.size +
+            installedWorkspaceItems.size +
+            installedHotseatItems.size +
+            firstScreenInstalledWidgets.size +
+            secondaryScreenInstalledWidgets.size
+
+    private fun FirstScreenBroadcastModel.printDebugInfo() {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                "Sending First Screen Broadcast for installer=$installerPackage" +
+                    ", total packages=${getTotalItemCount()}"
+            )
+            pendingCollectionItems.forEach {
+                Log.d(TAG, "$installerPackage:Pending Collection item:$it")
+            }
+            pendingWorkspaceItems.forEach {
+                Log.d(TAG, "$installerPackage:Pending Workspace item:$it")
+            }
+            pendingHotseatItems.forEach { Log.d(TAG, "$installerPackage:Pending Hotseat item:$it") }
+            pendingWidgetItems.forEach { Log.d(TAG, "$installerPackage:Pending Widget item:$it") }
+            installedWorkspaceItems.forEach {
+                Log.d(TAG, "$installerPackage:Installed Workspace item:$it")
+            }
+            installedHotseatItems.forEach {
+                Log.d(TAG, "$installerPackage:Installed Hotseat item:$it")
+            }
+            firstScreenInstalledWidgets.forEach {
+                Log.d(TAG, "$installerPackage:Installed Widget item (first screen):$it")
+            }
+            secondaryScreenInstalledWidgets.forEach {
+                Log.d(TAG, "$installerPackage:Installed Widget item (secondary screens):$it")
+            }
+        }
+    }
+
+    private fun getPackageName(info: ItemInfo): String? {
+        var packageName: String? = null
+        if (info is LauncherAppWidgetInfo) {
+            info.providerName?.let { packageName = info.providerName.packageName }
+        } else if (info.targetComponent != null) {
+            packageName = info.targetComponent?.packageName
+        }
+        return packageName
+    }
+
+    /**
+     * Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is
+     * always modified on UI thread.
+     */
+    @AnyThread
+    private fun cloneOnMainThread(list: ArrayList<WorkspaceItemInfo>): List<WorkspaceItemInfo> {
+        return try {
+            return Executors.MAIN_EXECUTOR.submit<ArrayList<WorkspaceItemInfo>> { ArrayList(list) }
+                .get()
+        } catch (e: Exception) {
+            emptyList()
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt b/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt
new file mode 100644
index 0000000..ba5c526
--- /dev/null
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.model
+
+/** Data model for the information used for [FirstScreenBroadcastHelper] Broadcast Extras */
+data class FirstScreenBroadcastModel(
+    // Package name of the installer for all items
+    val installerPackage: String,
+    // Installing items in Folders
+    val pendingCollectionItems: MutableSet<String> = mutableSetOf(),
+    // Installing items on first screen
+    val pendingWorkspaceItems: MutableSet<String> = mutableSetOf(),
+    // Installing items on hotseat
+    val pendingHotseatItems: MutableSet<String> = mutableSetOf(),
+    // Installing widgets on first screen
+    val pendingWidgetItems: MutableSet<String> = mutableSetOf(),
+    // Installed/Archived Items on first screen
+    val installedWorkspaceItems: MutableSet<String> = mutableSetOf(),
+    // Installed/Archived items on hotseat
+    val installedHotseatItems: MutableSet<String> = mutableSetOf(),
+    // Installed/Archived Widgets on first screen
+    val firstScreenInstalledWidgets: MutableSet<String> = mutableSetOf(),
+    // Installed Archived Widgets on secondary screens
+    val secondaryScreenInstalledWidgets: MutableSet<String> = mutableSetOf()
+)
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 0d40a24..dc6968c 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -47,6 +47,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -195,17 +196,32 @@
     }
 
     private void sendFirstScreenActiveInstallsBroadcast() {
-        ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
-        ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
-
         // Screen set is never empty
         IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
         final int firstScreen = allScreens.get(0);
         IntSet firstScreens = IntSet.wrap(firstScreen);
 
+        ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
+        ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
         filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
                 new ArrayList<>() /* otherScreenItems are ignored */);
-        mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
+        final int launcherBroadcastInstalledApps = Settings.Secure.getInt(
+                mApp.getContext().getContentResolver(),
+                "launcher_broadcast_installed_apps",
+                /* def= */ 0);
+        if (launcherBroadcastInstalledApps == 1) {
+            List<FirstScreenBroadcastModel> broadcastModels =
+                    FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                            mPmHelper,
+                            firstScreenItems,
+                            mInstallingPkgsCached,
+                            mBgDataModel.appWidgets
+                    );
+            logASplit("Sending first screen broadcast with additional archiving Extras");
+            FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
+        } else {
+            mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
+        }
     }
 
     public void run() {
@@ -249,7 +265,7 @@
             mModelDelegate.workspaceLoadComplete();
             // Notify the installer packages of packages with active installs on the first screen.
             sendFirstScreenActiveInstallsBroadcast();
-            logASplit("sendFirstScreenActiveInstallsBroadcast");
+            logASplit("sendFirstScreenBroadcast");
 
             // Take a break
             waitForIdle();
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 4cba0b5..454ae96 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.wm.shell.Flags;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -218,6 +219,7 @@
         // add and update.
         mWidgetsList.putAll(rawWidgetsShortcuts.stream()
                 .filter(new WidgetValidityCheck(app))
+                .filter(new WidgetFlagCheck())
                 .flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
                         .map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
                 .collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList()))));
@@ -377,6 +379,21 @@
         }
     }
 
+    private static class WidgetFlagCheck implements Predicate<WidgetItem> {
+
+        private static final String BUBBLES_SHORTCUT_WIDGET =
+                "com.android.systemui/com.android.wm.shell.bubbles.shortcut"
+                        + ".CreateBubbleShortcutActivity";
+
+        @Override
+        public boolean test(WidgetItem widgetItem) {
+            if (BUBBLES_SHORTCUT_WIDGET.equals(widgetItem.componentName.flattenToString())) {
+                return Flags.enableRetrievableBubbles();
+            }
+            return true;
+        }
+    }
+
     private static final class PackageItemInfoCache {
         private final Map<PackageUserKey, PackageItemInfo> mMap = new ArrayMap<>();
 
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index 4115b3d..e38824c 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -22,6 +22,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
 
@@ -38,6 +39,11 @@
     public final String[] personKeysFromNotification;
     public int count;
 
+    @VisibleForTesting
+    public NotificationKeyData(String notificationKey) {
+        this(notificationKey, null, 1, new String[]{});
+    }
+
     private NotificationKeyData(String notificationKey, String shortcutId, int count,
             String[] personKeysFromNotification) {
         this.notificationKey = notificationKey;
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index a4ff29f..21897bf 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -418,9 +418,7 @@
         DeviceGridState deviceGridState = new DeviceGridState(context);
         FileLog.d(TAG, "restore initiated from backup: DeviceGridState=" + deviceGridState);
         LauncherPrefs.get(context).putSync(RESTORE_DEVICE.to(deviceGridState.getDeviceType()));
-        if (enableLauncherBrMetricsFixed()) {
-            LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
-        }
+        LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
     }
 
     @WorkerThread
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index a01d402..b81729a 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -21,7 +21,7 @@
 import com.android.launcher3.views.ActivityContext;
 
 /**
- * Interface representing a state of a StatefulActivity
+ * Interface representing a state of a StatefulContainer
  */
 public interface BaseState<T extends BaseState> {
 
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index eea1a7d..ac07c0f 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -26,6 +26,7 @@
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
@@ -47,8 +48,11 @@
 /**
  * Class to manage transitions between different states for a StatefulActivity based on different
  * states
+ * @param STATE_TYPE Basestate used by the state manager
+ * @param STATEFUL_CONTAINER container object used to manage state
  */
-public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>> {
+public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>,
+        STATEFUL_CONTAINER extends Context & StatefulContainer<STATE_TYPE>> {
 
     public static final String TAG = "StateManager";
     // b/279059025, b/325463989
@@ -56,7 +60,7 @@
 
     private final AnimationState mConfig = new AnimationState();
     private final Handler mUiHandler;
-    private final StatefulActivity<STATE_TYPE> mActivity;
+    private final STATEFUL_CONTAINER mStatefulContainer;
     private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>();
     private final STATE_TYPE mBaseState;
 
@@ -71,12 +75,12 @@
 
     private STATE_TYPE mRestState;
 
-    public StateManager(StatefulActivity<STATE_TYPE> l, STATE_TYPE baseState) {
+    public StateManager(STATEFUL_CONTAINER container, STATE_TYPE baseState) {
         mUiHandler = new Handler(Looper.getMainLooper());
-        mActivity = l;
+        mStatefulContainer = container;
         mBaseState = baseState;
         mState = mLastStableState = mCurrentStableState = baseState;
-        mAtomicAnimationFactory = l.createAtomicAnimationFactory();
+        mAtomicAnimationFactory = container.createAtomicAnimationFactory();
     }
 
     public STATE_TYPE getState() {
@@ -109,10 +113,10 @@
         writer.println(prefix + "\tisInTransition:" + isInTransition());
     }
 
-    public StateHandler[] getStateHandlers() {
+    public StateHandler<STATE_TYPE>[] getStateHandlers() {
         if (mStateHandlers == null) {
-            ArrayList<StateHandler> handlers = new ArrayList<>();
-            mActivity.collectStateHandlers(handlers);
+            ArrayList<StateHandler<STATE_TYPE>> handlers = new ArrayList<>();
+            mStatefulContainer.collectStateHandlers(handlers);
             mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
         }
         return mStateHandlers;
@@ -130,7 +134,7 @@
      * Returns true if the state changes should be animated.
      */
     public boolean shouldAnimateStateChange() {
-        return !mActivity.isForceInvisible() && mActivity.isStarted();
+        return mStatefulContainer.shouldAnimateStateChange();
     }
 
     /**
@@ -245,7 +249,7 @@
         }
 
         animated &= areAnimatorsEnabled();
-        if (mActivity.isInState(state)) {
+        if (mStatefulContainer.isInState(state)) {
             if (mConfig.currentAnimation == null) {
                 // Run any queued runnable
                 if (listener != null) {
@@ -302,8 +306,8 @@
         // Since state mBaseState can be reached from multiple states, just assume that the
         // transition plays in reverse and use the same duration as previous state.
         mConfig.duration = state == mBaseState
-                ? fromState.getTransitionDuration(mActivity, false /* isToState */)
-                : state.getTransitionDuration(mActivity, true /* isToState */);
+                ? fromState.getTransitionDuration(mStatefulContainer, false /* isToState */)
+                : state.getTransitionDuration(mStatefulContainer, true /* isToState */);
         prepareForAtomicAnimation(fromState, state, mConfig);
         AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
         if (listener != null) {
@@ -336,7 +340,7 @@
         PendingAnimation builder = new PendingAnimation(config.duration);
         prepareForAtomicAnimation(fromState, toState, config);
 
-        for (StateHandler handler : mActivity.getStateManager().getStateHandlers()) {
+        for (StateHandler handler : mStatefulContainer.getStateManager().getStateHandlers()) {
             handler.setStateWithAnimation(toState, config, builder);
         }
         return builder.buildAnim();
@@ -402,7 +406,7 @@
 
     private void onStateTransitionStart(STATE_TYPE state) {
         mState = state;
-        mActivity.onStateSetStart(mState);
+        mStatefulContainer.onStateSetStart(mState);
 
         if (DEBUG) {
             Log.d(TAG, "onStateTransitionStart - state: " + state);
@@ -419,7 +423,7 @@
             mCurrentStableState = state;
         }
 
-        mActivity.onStateSetEnd(state);
+        mStatefulContainer.onStateSetEnd(state);
         if (state == mBaseState) {
             setRestState(null);
         }
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 30ba703..28f2def 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,7 +18,6 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
-import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
 import android.content.res.Configuration;
@@ -29,11 +28,9 @@
 
 import androidx.annotation.CallSuper;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherRootView;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.BaseDragLayer;
@@ -45,7 +42,7 @@
  * @param <STATE_TYPE> Type of state object
  */
 public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
-        extends BaseDraggingActivity {
+        extends BaseDraggingActivity implements StatefulContainer<STATE_TYPE> {
 
     public final Handler mHandler = new Handler();
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -67,19 +64,9 @@
     /**
      * Create handlers to control the property changes for this activity
      */
-    protected abstract void collectStateHandlers(List<StateHandler> out);
 
-    /**
-     * Returns true if the activity is in the provided state
-     */
-    public boolean isInState(STATE_TYPE state) {
-        return getStateManager().getState() == state;
-    }
-
-    /**
-     * Returns the state manager for this activity
-     */
-    public abstract StateManager<STATE_TYPE> getStateManager();
+    @Override
+    public abstract void collectStateHandlers(List<StateHandler<STATE_TYPE>> out);
 
     protected void inflateRootView(int layoutId) {
         mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null);
@@ -106,22 +93,12 @@
         if (mDeferredResumePending) {
             handleDeferredResume();
         }
-
-        if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
-            AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
-        }
+        StatefulContainer.super.onStateSetStart(state);
     }
 
-    /**
-     * Called when transition to state ends
-     */
-    public void onStateSetEnd(STATE_TYPE state) { }
-
-    /**
-     * Creates a factory for atomic state animations
-     */
-    public AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
-        return new AtomicAnimationFactory(0);
+    @Override
+    public boolean shouldAnimateStateChange() {
+        return !isForceInvisible() && isStarted();
     }
 
     @Override
diff --git a/src/com/android/launcher3/statemanager/StatefulContainer.java b/src/com/android/launcher3/statemanager/StatefulContainer.java
new file mode 100644
index 0000000..0cf0a27
--- /dev/null
+++ b/src/com/android/launcher3/statemanager/StatefulContainer.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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.statemanager;
+
+
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
+import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
+
+import androidx.annotation.CallSuper;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.List;
+
+/**
+ * Interface for a container that can be managed by a state manager.
+ *
+ * @param <STATE_TYPE> The type of state that the container can be in.
+ */
+public interface StatefulContainer<STATE_TYPE extends BaseState<STATE_TYPE>> extends
+        ActivityContext {
+
+    /**
+     * Creates a factory for atomic state animations
+     */
+    default StateManager.AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
+        return new StateManager.AtomicAnimationFactory<>(0);
+    }
+
+    /**
+     * Create handlers to control the property changes for this activity
+     */
+    void collectStateHandlers(List<StateManager.StateHandler<STATE_TYPE>> out);
+
+    /**
+     * Retrieves state manager for given container
+     */
+    StateManager<STATE_TYPE, ?> getStateManager();
+
+    /**
+     * Called when transition to state ends
+     * @param state current state of State_Type
+     */
+    default void onStateSetEnd(STATE_TYPE state) { }
+
+    /**
+     * Called when transition to state starts
+     * @param state current state of State_Type
+     */
+    @CallSuper
+    default void onStateSetStart(STATE_TYPE state) {
+        if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
+            AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
+        }
+    }
+
+    /**
+     * Returns true if the activity is in the provided state
+     * @param state current state of State_Type
+     */
+    default boolean isInState(STATE_TYPE state) {
+        return getStateManager().getState() == state;
+    }
+
+    /**
+     * Returns true if state change should transition with animation
+     */
+    boolean shouldAnimateStateChange();
+}
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 6429a43..095518c 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -16,10 +16,12 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_HOME_ROLE;
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
 import android.app.ActivityOptions;
 import android.app.Person;
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
@@ -33,6 +35,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
@@ -136,6 +139,23 @@
         return false;
     }
 
+    /**
+     * Starts an Activity which can be used to set this Launcher as the HOME app, via a consent
+     * screen. In case the consent screen cannot be shown, or the user does not set current Launcher
+     * as HOME app, a toast asking the user to do the latter is shown.
+     */
+    public void assignDefaultHomeRole(Context context) {
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        assert roleManager != null;
+        if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME)
+                && !roleManager.isRoleHeld(RoleManager.ROLE_HOME)) {
+            Intent roleRequestIntent = roleManager.createRequestRoleIntent(
+                    RoleManager.ROLE_HOME);
+            Launcher launcher = Launcher.getLauncher(context);
+            launcher.startActivityForResult(roleRequestIntent, REQUEST_HOME_ROLE);
+        }
+    }
+
     @Override
     public void close() { }
 
diff --git a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
index 85f81f5..749caac 100644
--- a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
+++ b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
@@ -113,10 +113,9 @@
         // flags accordingly
         animationController =
             controller.apply {
-                activity.stateManager.setCurrentAnimation(
-                    this,
-                    USER_CONTROLLED or HANDLE_STATE_APPLY
-                )
+                activity
+                    .stateManager
+                    .setCurrentAnimation(this, USER_CONTROLLED or HANDLE_STATE_APPLY)
             }
         recreateAnimation(provider)
     }
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index f7c4df4..8c5a76e 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,9 +16,10 @@
 
 package com.android.launcher3.util;
 
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
 import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
 
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
+
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -151,6 +152,18 @@
     }
 
     /**
+     * Returns the installing app package for the given package
+     */
+    public String getAppInstallerPackage(@NonNull final String packageName) {
+        try {
+            return mPm.getInstallSourceInfo(packageName).getInstallingPackageName();
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Failed to get installer package for app package:" + packageName, e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the application info for the provided package or null
      */
     @Nullable
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 3405635..8770859 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -57,7 +57,7 @@
  *
  * For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
  */
-@AllowedDevices(allowed = [DeviceProduct.CF_PHONE, DeviceProduct.HOST_SIDE_X86_64])
+@AllowedDevices(allowed = [DeviceProduct.CF_PHONE, DeviceProduct.ROBOLECTRIC])
 @IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
 abstract class AbstractDeviceProfileTest {
     protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context
@@ -341,4 +341,9 @@
     protected fun Int.dpToPx(): Int {
         return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
     }
+
+    protected fun String.xmlToId(): Int {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        return context.resources.getIdentifier(this, "xml", context.packageName)
+    }
 }
diff --git a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
index c99da96..3dca35e 100644
--- a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
@@ -22,7 +22,6 @@
 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
@@ -45,7 +44,7 @@
     fun parseValidFile() {
         val allAppsSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_all_apps_file),
+                TestResourceHelper(context, "valid_all_apps_file".xmlToId()),
                 ResponsiveSpecType.AllApps
             )
 
@@ -114,7 +113,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingTag_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_1),
+            TestResourceHelper(context, "invalid_all_apps_file_case_1".xmlToId()),
             ResponsiveSpecType.AllApps
         )
     }
@@ -122,7 +121,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_2),
+            TestResourceHelper(context, "invalid_all_apps_file_case_2".xmlToId()),
             ResponsiveSpecType.AllApps
         )
     }
@@ -130,7 +129,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_valueBiggerThan1_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_3),
+            TestResourceHelper(context, "invalid_all_apps_file_case_3".xmlToId()),
             ResponsiveSpecType.AllApps
         )
     }
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
index 1cc5ed2..8346492 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
@@ -23,7 +23,6 @@
 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
 import org.junit.Test
@@ -52,7 +51,7 @@
 
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                TestResourceHelper(context, "valid_workspace_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
         val widthSpec =
@@ -62,7 +61,7 @@
 
         val allAppsSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_all_apps_file),
+                TestResourceHelper(context, "valid_all_apps_file".xmlToId()),
                 ResponsiveSpecType.AllApps
             )
 
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
index c4e2d2a..46d6cb9 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
@@ -23,7 +23,6 @@
 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
@@ -47,12 +46,12 @@
         val columns = 6
 
         // Loading workspace specs
-        val resourceHelperWorkspace = TestResourceHelper(context, R.xml.valid_workspace_file)
+        val resourceHelperWorkspace = TestResourceHelper(context, "valid_workspace_file".xmlToId())
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(resourceHelperWorkspace, ResponsiveSpecType.Workspace)
 
         // Loading folders specs
-        val resourceHelperFolder = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelperFolder = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs =
             ResponsiveSpecsProvider.create(resourceHelperFolder, ResponsiveSpecType.Folder)
         val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
@@ -123,12 +122,12 @@
         val rows = 5
 
         // Loading workspace specs
-        val resourceHelperWorkspace = TestResourceHelper(context, R.xml.valid_workspace_file)
+        val resourceHelperWorkspace = TestResourceHelper(context, "valid_workspace_file".xmlToId())
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(resourceHelperWorkspace, ResponsiveSpecType.Workspace)
 
         // Loading folders specs
-        val resourceHelperFolder = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelperFolder = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs =
             ResponsiveSpecsProvider.create(resourceHelperFolder, ResponsiveSpecType.Folder)
         val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
index 1a564ac..5ab44f3 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
@@ -22,7 +22,6 @@
 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
 import org.junit.Test
@@ -47,7 +46,7 @@
         val availableHeight = deviceSpec.naturalSize.second
 
         val hotseatSpecsProvider =
-            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+            HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
         val heightSpec =
             hotseatSpecsProvider.getCalculatedSpec(
                 aspectRatio,
@@ -73,7 +72,7 @@
         val availableHeight = deviceSpec.naturalSize.second
 
         val hotseatSpecsProvider =
-            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+            HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
         val heightSpec =
             hotseatSpecsProvider.getCalculatedSpec(
                 aspectRatio,
@@ -99,7 +98,7 @@
 
         val hotseatSpecsProvider =
             HotseatSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_hotseat_land_file)
+                TestResourceHelper(context, "valid_hotseat_land_file".xmlToId())
             )
         val widthSpec =
             hotseatSpecsProvider.getCalculatedSpec(aspectRatio, DimensionType.WIDTH, availableWidth)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
index 0c5d347..dea98b6 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
@@ -23,7 +23,6 @@
 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
 import org.junit.Test
@@ -53,7 +52,7 @@
 
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                TestResourceHelper(context, "valid_workspace_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
         val widthSpec =
@@ -96,7 +95,7 @@
 
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                TestResourceHelper(context, "valid_workspace_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
         val widthSpec =
@@ -138,7 +137,7 @@
         val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_unsorted_file),
+                TestResourceHelper(context, "valid_workspace_unsorted_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
         val widthSpec =
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
similarity index 91%
rename from tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
index 5cfa49f..d2b264b 100644
--- a/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
@@ -23,7 +23,6 @@
 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
@@ -44,7 +43,7 @@
 
     @Test
     fun parseValidFile() {
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
 
@@ -92,25 +91,25 @@
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingTag_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_1)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_1".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_2)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_2".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_valueBiggerThan1_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_3)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_3".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingSpecs_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_4)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_4".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
     }
 
@@ -132,7 +131,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_5)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_5".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         folderSpecs.getCalculatedSpec(
             aspectRatio,
@@ -161,7 +160,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_5)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_5".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         folderSpecs.getCalculatedSpec(
             aspectRatio,
@@ -190,7 +189,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         val calculatedWidthSpec =
             folderSpecs.getCalculatedSpec(
@@ -227,7 +226,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         folderSpecs.getCalculatedSpec(
             aspectRatio,
@@ -256,7 +255,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         val calculatedHeightSpec =
             folderSpecs.getCalculatedSpec(
@@ -293,7 +292,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         folderSpecs.getCalculatedSpec(
             aspectRatio,
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
index 78cb1ac..58324e1 100644
--- a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
@@ -22,7 +22,6 @@
 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
@@ -44,7 +43,7 @@
     @Test
     fun parseValidFile() {
         val hotseatSpecsProvider =
-            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+            HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
         val specs = hotseatSpecsProvider.getSpecsByAspectRatio(aspectRatio)
 
         val expectedHeightSpecs =
@@ -76,7 +75,7 @@
     fun parseValidLandscapeFile() {
         val hotseatSpecsProvider =
             HotseatSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_hotseat_land_file)
+                TestResourceHelper(context, "valid_hotseat_land_file".xmlToId())
             )
         val specs = hotseatSpecsProvider.getSpecsByAspectRatio(aspectRatio)
         assertThat(specs.heightSpecs.size).isEqualTo(0)
@@ -107,14 +106,14 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_spaceIsNotFixedSize_throwsError() {
         HotseatSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_1)
+            TestResourceHelper(context, "invalid_hotseat_file_case_1".xmlToId())
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidFixedSize_throwsError() {
         HotseatSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_2)
+            TestResourceHelper(context, "invalid_hotseat_file_case_2".xmlToId())
         )
     }
 }
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
similarity index 92%
rename from tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
index 50cd358..11161bd 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
@@ -22,7 +22,6 @@
 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
@@ -43,7 +42,7 @@
 
     @Test
     fun parseValidFile() {
-        val testResourceHelper = TestResourceHelper(context, TestR.xml.valid_cell_specs_file)
+        val testResourceHelper = TestResourceHelper(context, "valid_cell_specs_file".xmlToId())
         val provider = ResponsiveCellSpecsProvider.create(testResourceHelper)
 
         // Validate Portrait
@@ -98,21 +97,21 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_IsNotFixedSizeOrMatchWorkspace_throwsError() {
         ResponsiveCellSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_cell_specs_1)
+            TestResourceHelper(context, "invalid_cell_specs_1".xmlToId())
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_dimensionTypeIsNotHeight_throwsError() {
         ResponsiveCellSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_cell_specs_2)
+            TestResourceHelper(context, "invalid_cell_specs_2".xmlToId())
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidFixedSize_throwsError() {
         ResponsiveCellSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_cell_specs_3)
+            TestResourceHelper(context, "invalid_cell_specs_3".xmlToId())
         )
     }
 }
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
index 54a1dc5..c74f7a8 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
@@ -23,7 +23,6 @@
 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
@@ -44,7 +43,7 @@
 
     @Test
     fun parseValidFile() {
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_responsive_spec_unsorted)
+        val resourceHelper = TestResourceHelper(context, "valid_responsive_spec_unsorted".xmlToId())
         val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
 
         // Validate Portrait
@@ -111,44 +110,44 @@
 
     @Test(expected = IllegalStateException::class)
     fun parseValidFile_invalidAspectRatio_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_responsive_spec_unsorted)
+        val resourceHelper = TestResourceHelper(context, "valid_responsive_spec_unsorted".xmlToId())
         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)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_1".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = InvalidResponsiveGridSpec::class)
     fun parseInvalidFile_partialGroups_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_2)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_2".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidAspectRatio_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_3)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_3".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidRemainderSpace_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_4)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_4".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidAvailableSpace_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_5)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_5".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidFixedSize_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_6)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_6".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
diff --git a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
index 17b0ee4..bc133ba 100644
--- a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
@@ -22,7 +22,6 @@
 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
@@ -45,7 +44,7 @@
     fun parseValidFile() {
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                TestResourceHelper(context, "valid_workspace_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
 
@@ -169,7 +168,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingTag_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_1),
+            TestResourceHelper(context, "invalid_workspace_file_case_1".xmlToId()),
             ResponsiveSpecType.Workspace
         )
     }
@@ -177,7 +176,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_2),
+            TestResourceHelper(context, "invalid_workspace_file_case_2".xmlToId()),
             ResponsiveSpecType.Workspace
         )
     }
@@ -185,7 +184,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_valueBiggerThan1_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_3),
+            TestResourceHelper(context, "invalid_workspace_file_case_3".xmlToId()),
             ResponsiveSpecType.Workspace
         )
     }
@@ -193,7 +192,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_matchWorkspace_true_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_4),
+            TestResourceHelper(context, "invalid_workspace_file_case_4".xmlToId()),
             ResponsiveSpecType.Workspace
         )
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt
new file mode 100644
index 0000000..d95c6f8
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.R
+import kotlin.IntArray
+
+class TestResourceHelper(private val context: Context, specsFileId: Int) :
+    ResourceHelper(context, specsFileId) {
+
+    val responsiveStyleables = listOf(
+            R.styleable.SizeSpec,
+            R.styleable.WorkspaceSpec,
+            R.styleable.FolderSpec,
+            R.styleable.AllAppsSpec,
+            R.styleable.ResponsiveSpecGroup
+    )
+
+    override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
+        val clone =
+                if (responsiveStyleables.any { styleId.contentEquals(it) }) {
+                    convertStyleId(styleId)
+                } else {
+                    styleId.clone()
+                }
+
+        return context.obtainStyledAttributes(attrs, clone)
+    }
+
+    private fun convertStyleId(styleableArr: IntArray): IntArray {
+        val targetContextRes = getInstrumentation().targetContext.resources
+        val context = getInstrumentation().context
+        return styleableArr
+            .map { attrId -> targetContextRes.getResourceName(attrId).split(":").last() }
+            .map { attrName ->
+                // Get required attr from context instead of targetContext
+                context.resources.getIdentifier(attrName, null, context.packageName)
+            }
+            .toIntArray()
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
new file mode 100644
index 0000000..aadf72e
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2024 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.model
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageInstaller.SessionInfo
+import android.os.UserHandle
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.model.FirstScreenBroadcastHelper.MAX_BROADCAST_SIZE
+import com.android.launcher3.model.FirstScreenBroadcastHelper.getTotalItemCount
+import com.android.launcher3.model.FirstScreenBroadcastHelper.truncateModelForBroadcast
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+class FirstScreenBroadcastHelperTest {
+    private val context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
+    private val mockPmHelper = mock<PackageManagerHelper>()
+    private val expectedAppPackage = "appPackageExpected"
+    private val expectedComponentName = ComponentName(expectedAppPackage, "expectedClass")
+    private val expectedInstallerPackage = "installerPackage"
+    private val expectedIntent =
+        Intent().apply {
+            component = expectedComponentName
+            setPackage(expectedAppPackage)
+        }
+    private val unexpectedAppPackage = "appPackageUnexpected"
+    private val unexpectedComponentName = ComponentName(expectedAppPackage, "unexpectedClass")
+    private val firstScreenItems =
+        listOf(
+            WorkspaceItemInfo().apply {
+                container = CONTAINER_DESKTOP
+                intent = expectedIntent
+            },
+            WorkspaceItemInfo().apply {
+                container = CONTAINER_HOTSEAT
+                intent = expectedIntent
+            },
+            LauncherAppWidgetInfo().apply { providerName = expectedComponentName }
+        )
+
+    @Test
+    fun `Broadcast Models are created with Pending Items from first screen`() {
+        // Given
+        val sessionInfoExpected =
+            SessionInfo().apply {
+                installerPackageName = expectedInstallerPackage
+                appPackageName = expectedAppPackage
+            }
+        val sessionInfoUnexpected =
+            SessionInfo().apply {
+                installerPackageName = expectedInstallerPackage
+                appPackageName = unexpectedAppPackage
+            }
+        val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
+            hashMapOf(
+                PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
+                PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected
+            )
+
+        // When
+        val actualResult =
+            FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                packageManagerHelper = mockPmHelper,
+                firstScreenItems = firstScreenItems,
+                userKeyToSessionMap = sessionInfoMap,
+                allWidgets = listOf()
+            )
+
+        // Then
+        val expectedResult =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    pendingWorkspaceItems = mutableSetOf(expectedAppPackage),
+                    pendingHotseatItems = mutableSetOf(expectedAppPackage),
+                    pendingWidgetItems = mutableSetOf(expectedAppPackage)
+                )
+            )
+
+        assertEquals(expectedResult, actualResult)
+    }
+
+    @Test
+    fun `Broadcast Models are created with Installed Items from first screen`() {
+        // Given
+        whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage))
+            .thenReturn(expectedInstallerPackage)
+
+        // When
+        val actualResult =
+            FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                packageManagerHelper = mockPmHelper,
+                firstScreenItems = firstScreenItems,
+                userKeyToSessionMap = hashMapOf(),
+                allWidgets =
+                    listOf(
+                        LauncherAppWidgetInfo().apply {
+                            providerName = expectedComponentName
+                            screenId = 0
+                        }
+                    )
+            )
+
+        // Then
+        val expectedResult =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    installedHotseatItems = mutableSetOf(expectedAppPackage),
+                    installedWorkspaceItems = mutableSetOf(expectedAppPackage),
+                    firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage)
+                )
+            )
+        assertEquals(expectedResult, actualResult)
+    }
+
+    @Test
+    fun `Broadcast Models are created with Installed Widgets from every screen`() {
+        // Given
+        val expectedAppPackage2 = "appPackageExpected2"
+        val expectedComponentName2 = ComponentName(expectedAppPackage2, "expectedClass2")
+        whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage))
+            .thenReturn(expectedInstallerPackage)
+        whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage2))
+            .thenReturn(expectedInstallerPackage)
+
+        // When
+        val actualResult =
+            FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                packageManagerHelper = mockPmHelper,
+                firstScreenItems = listOf(),
+                userKeyToSessionMap = hashMapOf(),
+                allWidgets =
+                    listOf(
+                        LauncherAppWidgetInfo().apply {
+                            providerName = expectedComponentName
+                            screenId = 0
+                        },
+                        LauncherAppWidgetInfo().apply {
+                            providerName = expectedComponentName2
+                            screenId = 1
+                        },
+                        LauncherAppWidgetInfo().apply {
+                            providerName = unexpectedComponentName
+                            screenId = 0
+                        }
+                    )
+            )
+
+        // Then
+        val expectedResult =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    installedHotseatItems = mutableSetOf(),
+                    installedWorkspaceItems = mutableSetOf(),
+                    firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage),
+                    secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2)
+                )
+            )
+        assertEquals(expectedResult, actualResult)
+    }
+
+    @Test
+    fun `Broadcast Models are created with Pending Items in Collections from the first screen`() {
+        // Given
+        val sessionInfoExpected =
+            SessionInfo().apply {
+                installerPackageName = expectedInstallerPackage
+                appPackageName = expectedAppPackage
+            }
+        val sessionInfoUnexpected =
+            SessionInfo().apply {
+                installerPackageName = expectedInstallerPackage
+                appPackageName = unexpectedAppPackage
+            }
+        val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
+            hashMapOf(
+                PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
+                PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
+            )
+        val expectedItemInfo = WorkspaceItemInfo().apply { intent = expectedIntent }
+        val expectedFolderInfo = FolderInfo().apply { add(expectedItemInfo) }
+        val firstScreenItems = listOf(expectedFolderInfo)
+
+        // When
+        val actualResult =
+            FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                packageManagerHelper = mockPmHelper,
+                firstScreenItems = firstScreenItems,
+                userKeyToSessionMap = sessionInfoMap,
+                allWidgets = listOf()
+            )
+
+        // Then
+        val expectedResult =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    pendingCollectionItems = mutableSetOf(expectedAppPackage)
+                )
+            )
+        assertEquals(expectedResult, actualResult)
+    }
+
+    @Test
+    fun `Models with too many items get truncated to max Broadcast size`() {
+        // given
+        val broadcastModel =
+            FirstScreenBroadcastModel(
+                installerPackage = expectedInstallerPackage,
+                pendingCollectionItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                pendingWorkspaceItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                pendingHotseatItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                pendingWidgetItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                installedWorkspaceItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                installedHotseatItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                firstScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                secondaryScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }
+            )
+
+        // When
+        broadcastModel.truncateModelForBroadcast()
+
+        // Then
+        assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+    }
+
+    @Test
+    fun `Broadcast truncates installed Hotseat items before other installed items`() {
+        // Given
+        val broadcastModel =
+            FirstScreenBroadcastModel(
+                installerPackage = expectedInstallerPackage,
+                installedWorkspaceItems =
+                    mutableSetOf<String>().apply { repeat(50) { add(it.toString()) } },
+                firstScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } },
+                secondaryScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(10) { add((it + 10).toString()) } },
+                installedHotseatItems =
+                    mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } },
+            )
+
+        // When
+        broadcastModel.truncateModelForBroadcast()
+
+        // Then
+        assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+        assertEquals(50, broadcastModel.installedWorkspaceItems.size)
+        assertEquals(10, broadcastModel.firstScreenInstalledWidgets.size)
+        assertEquals(10, broadcastModel.secondaryScreenInstalledWidgets.size)
+        assertEquals(0, broadcastModel.installedHotseatItems.size)
+    }
+
+    @Test
+    fun `Broadcast truncates Widgets before the rest of the first screen items`() {
+        // Given
+        val broadcastModel =
+            FirstScreenBroadcastModel(
+                installerPackage = expectedInstallerPackage,
+                installedWorkspaceItems =
+                    mutableSetOf<String>().apply { repeat(70) { add(it.toString()) } },
+                firstScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                secondaryScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+            )
+
+        // When
+        broadcastModel.truncateModelForBroadcast()
+
+        // Then
+        assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+        assertEquals(70, broadcastModel.installedWorkspaceItems.size)
+        assertEquals(0, broadcastModel.firstScreenInstalledWidgets.size)
+        assertEquals(0, broadcastModel.secondaryScreenInstalledWidgets.size)
+    }
+
+    @Test
+    fun `Broadcasts are correctly formed with Extras for each Installer`() {
+        // Given
+        val broadcastModels: List<FirstScreenBroadcastModel> =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    pendingCollectionItems = mutableSetOf("pendingCollectionItem"),
+                    pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"),
+                    pendingHotseatItems = mutableSetOf("pendingHotseatItems"),
+                    pendingWidgetItems = mutableSetOf("pendingWidgetItems"),
+                    installedWorkspaceItems = mutableSetOf("installedWorkspaceItems"),
+                    installedHotseatItems = mutableSetOf("installedHotseatItems"),
+                    firstScreenInstalledWidgets = mutableSetOf("firstScreenInstalledWidgetItems"),
+                    secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems")
+                )
+            )
+        val expectedPendingIntent =
+            PendingIntent.getActivity(
+                context,
+                0 /* requestCode */,
+                Intent(),
+                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+            )
+
+        // When
+        FirstScreenBroadcastHelper.sendBroadcastsForModels(context, broadcastModels)
+
+        // Then
+        val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(context).sendBroadcast(argumentCaptor.capture())
+
+        assertEquals(
+            "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS",
+            argumentCaptor.value.action
+        )
+        assertEquals(expectedInstallerPackage, argumentCaptor.value.`package`)
+        assertEquals(
+            expectedPendingIntent,
+            argumentCaptor.value.getParcelableExtra("verificationToken")
+        )
+        assertEquals(
+            arrayListOf("pendingCollectionItem"),
+            argumentCaptor.value.getStringArrayListExtra("folderItem")
+        )
+        assertEquals(
+            arrayListOf("pendingWorkspaceItem"),
+            argumentCaptor.value.getStringArrayListExtra("workspaceItem")
+        )
+        assertEquals(
+            arrayListOf("pendingHotseatItems"),
+            argumentCaptor.value.getStringArrayListExtra("hotseatItem")
+        )
+        assertEquals(
+            arrayListOf("pendingWidgetItems"),
+            argumentCaptor.value.getStringArrayListExtra("widgetItem")
+        )
+        assertEquals(
+            arrayListOf("installedWorkspaceItems"),
+            argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems")
+        )
+        assertEquals(
+            arrayListOf("installedHotseatItems"),
+            argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems")
+        )
+        assertEquals(
+            arrayListOf("firstScreenInstalledWidgetItems", "secondaryInstalledWidgetItems"),
+            argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems")
+        )
+    }
+}
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
index 4a67600..342eedf 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
@@ -21,9 +21,8 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.Launcher;
-import com.android.launcher3.util.rule.ScreenRecordRule.KeepRecordOnSuccess;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.Launcher;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,7 +31,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsLauncher3Test extends AbstractLauncherUiTest<Launcher> {
 
-    @KeepRecordOnSuccess
     @ScreenRecord // b/322823478
     @Test
     public void testDevicePressMenu() throws Exception {
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index 69b42cb..b2e413d 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -45,7 +45,6 @@
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ScreenRecordRule.KeepRecordOnSuccess;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 
@@ -196,7 +195,6 @@
 
     }
 
-    @KeepRecordOnSuccess
     @ScreenRecord // b/322823478
     @Test
     public void testEdu() {
diff --git a/tests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
deleted file mode 100644
index b4d3ba8..0000000
--- a/tests/src/com/android/launcher3/util/TestResourceHelper.kt
+++ /dev/null
@@ -1,42 +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.util
-
-import android.content.Context
-import android.content.res.TypedArray
-import android.util.AttributeSet
-import com.android.launcher3.R
-import com.android.launcher3.tests.R as TestR
-import kotlin.IntArray
-
-class TestResourceHelper(private val context: Context, specsFileId: Int) :
-    ResourceHelper(context, specsFileId) {
-    override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
-        val clone =
-            when {
-                styleId.contentEquals(R.styleable.SizeSpec) -> TestR.styleable.SizeSpec
-                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()
-            }
-
-        return context.obtainStyledAttributes(attrs, clone)
-    }
-}
diff --git a/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java b/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java
index ff96afb..7a5cf2c 100644
--- a/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java
@@ -49,9 +49,6 @@
             return base;
         }
 
-        final Boolean keepRecordOnSuccess = description.getAnnotation(KeepRecordOnSuccess.class)
-                != null;
-
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
@@ -73,7 +70,7 @@
                     device.executeShellCommand("kill -INT " + screenRecordPid);
                     Log.e(TAG, "Screenrecord captured at: " + outputFile);
                     output.close();
-                    if (success && !keepRecordOnSuccess) {
+                    if (success) {
                         automation.executeShellCommand("rm " + outputFile);
                     }
                 }
@@ -88,14 +85,4 @@
     @Target(ElementType.METHOD)
     public @interface ScreenRecord {
     }
-
-
-    /**
-     * Interface to indicate that we should keep the screen record even if the test succeeds, use
-     * sparingly since it uses a lof of memory.
-     */
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target(ElementType.METHOD)
-    public @interface KeepRecordOnSuccess {
-    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 4be46ab..c58d16e 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -17,10 +17,12 @@
 
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
-import android.text.TextUtils;
+import android.graphics.Rect;
 import android.widget.TextView;
 
 import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
 import java.util.ArrayList;
@@ -34,10 +36,12 @@
     // This particular ID change should happen with caution
     private static final String SEARCH_CONTAINER_RES_ID = "search_results_list_view";
     protected final LauncherInstrumentation mLauncher;
+    private final UiObject2 mSearchContainer;
 
     SearchResultFromQsb(LauncherInstrumentation launcher) {
         mLauncher = launcher;
         mLauncher.waitForLauncherObject("search_container_all_apps");
+        mSearchContainer = mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID);
     }
 
     /** Find the app from search results with app name. */
@@ -52,18 +56,9 @@
 
     /** Find the web suggestion from search suggestion's title text */
     public SearchWebSuggestion findWebSuggestion(String text) {
-        ArrayList<UiObject2> webSuggestions =
-                new ArrayList<>(mLauncher.waitForObjectsInContainer(
-                        mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
-                        By.clazz(TextView.class)));
-        for (UiObject2 uiObject: webSuggestions) {
-            String currentString = uiObject.getText();
-            if (currentString.equals(text)) {
-                return createWebSuggestion(uiObject);
-            }
-        }
-        mLauncher.fail("Web suggestion title: " + text + " not found");
-        return null;
+        UiObject2 webSuggestion = mLauncher.waitForObjectInContainer(mSearchContainer,
+                getSearchResultSelector(text));
+        return createWebSuggestion(webSuggestion);
     }
 
     protected SearchWebSuggestion createWebSuggestion(UiObject2 webSuggestion) {
@@ -73,13 +68,25 @@
     /** Find the total amount of views being displayed and return the size */
     public int getSearchResultItemSize() {
         ArrayList<UiObject2> searchResultItems =
-                new ArrayList<>(mLauncher.waitForObjectsInContainer(
-                        mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
+                new ArrayList<>(mLauncher.waitForObjectsInContainer(mSearchContainer,
                         By.clazz(TextView.class)));
         return searchResultItems.size();
     }
 
     /**
+     * Scroll down to make next page search results rendered.
+     */
+    public void getNextPageSearchResults() {
+        final int searchContainerHeight = mLauncher.getVisibleBounds(mSearchContainer).height();
+        // Start scrolling from center of the screen to top of the screen.
+        mLauncher.scroll(mSearchContainer,
+                Direction.DOWN,
+                new Rect(0, 0, 0, searchContainerHeight / 2),
+                /* steps= */ 10,
+                /*slowDown= */ false);
+    }
+
+    /**
      * Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only.
      * @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
      */
@@ -121,25 +128,13 @@
 
     /** Verify a tile is present by checking its title and subtitle. */
     public void verifyTileIsPresent(String title, String subtitle) {
-        ArrayList<UiObject2> searchResults =
-                new ArrayList<>(mLauncher.waitForObjectsInContainer(
-                        mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
-                        By.clazz(TextView.class)));
-        boolean foundTitle = false;
-        boolean foundSubtitle = false;
-        for (UiObject2 uiObject: searchResults) {
-            String currentString = uiObject.getText();
-            if (TextUtils.equals(currentString, title)) {
-                foundTitle = true;
-            } else if (TextUtils.equals(currentString, subtitle)) {
-                foundSubtitle = true;
-            }
-        }
-        if (!foundTitle) {
-            mLauncher.fail("Tile not found for title: " + title);
-        }
-        if (!foundSubtitle) {
-            mLauncher.fail("Tile not found for subtitle: " + subtitle);
-        }
+        mLauncher.waitForObjectInContainer(mSearchContainer,
+                getSearchResultSelector(title));
+        mLauncher.waitForObjectInContainer(mSearchContainer,
+                getSearchResultSelector(subtitle));
+    }
+
+    private BySelector getSearchResultSelector(String text) {
+        return By.clazz(TextView.class).text(text);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 4fa93ef..9ac6768 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -444,6 +444,8 @@
             Runnable expectLongClickEvents) {
         try (LauncherInstrumentation.Closable c = launcher.addContextLayer(
                 "uninstalling app icon")) {
+
+            final String appNameToUninstall = homeAppIcon.getAppName();
             dragIconToWorkspace(
                     launcher,
                     homeAppIcon,
@@ -468,7 +470,10 @@
 
             try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
                     "uninstalled app by dragging to the drop bar")) {
-                return new Workspace(launcher);
+                final Workspace newWorkspace = new Workspace(launcher);
+                launcher.waitUntilLauncherObjectGone(
+                        AppIcon.getAppIconSelector(appNameToUninstall));
+                return newWorkspace;
             }
         }
     }